• 智能指针梳理


    RAII

    RAII(Resource Acquisition Is Initialization)资源获取即初始化。

    它是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

    在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。至此我们可以看到实际上就是把一份资源的责任托管给了一个对象。

    这种做法有两大好处:

    1. 不需要显式释放资源。析构时自动调用。

    2. 采用这种方式,对象所需的资源在其生命期内始终保持有效。

    智能指针是RAII思想的一种具像

    c++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被c++11弃用

    一、shared_ptr

    std::shared_ptr也即智能指针,采用RAII手法,是一个模版对象。std::shared_ptr表示某一个资源的共享所有权。

    线程安全

    shared_ptr指针类有两个成员变量,一个是指向变量的指针;一个是资源被引用的次数,引用次数加减操作内部自动加锁解锁,是线程安全的。

    指针和引用计数是线程安全的,但指针所指对象中的操作就需要自己做控制,并不是线程安全的。因为shared_ptr 有两个数据成员(指向被管理对象的指针,和指向控制块的指针),读写操作不能原子化。

    线程安全case1

    多线程读写shared_ptr所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护

    1. shared_ptr<long> global_instance = make_shared<long>(0);
    2. std::mutex g_i_mutex;
    3. void thread_fcn()
    4. {
    5. //std::lock_guard lock(g_i_mutex);
    6. //shared_ptr local = global_instance;
    7. for(int i = 0; i < 100000000; i++)
    8. {
    9. *global_instance = *global_instance + 1;
    10. //*local = *local + 1;
    11. }
    12. }
    13. int main(int argc, char** argv)
    14. {
    15. thread thread1(thread_fcn);
    16. thread thread2(thread_fcn);
    17. thread1.join();
    18. thread2.join();
    19. cout << "*global_instance is " << *global_instance << endl;
    20. return 0;
    21. }

    在线程函数thread_fcn的for循环中,2个线程同时对*global_instance进行加1的操作。这就是典型的非线程安全的场景,最后的结果是未定的,运行结果如下:

    *global_instance is 197240539

    如果使用的是每个线程的局部shared_ptr对象local,因为这些local指向相同的对象,因此结果也是未定的,运行结果如下:

    *global_instance is 160285803

    因此,这种情况下必须加锁,将thread_fcn中的第一行代码的注释去掉之后,不管是使用global_instance,还是使用local,得到的结果都是:

    *global_instance is 200000000

    线程安全case 2

    • 一个 shared_ptr 对象实体可被多个线程同时读取(文档例1);

    • 两个 shared_ptr 对象实体可以被两个线程同时写入(例2),“析构”算写操作;

    • 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁(例3~5)。

    请注意,以上是 shared_ptr 对象本身的线程安全级别,不是它管理的对象的线程安全级别。

    shared_ptr g(new Foo); // 线程之间共享的 shared_ptr

    shared_ptr x; // 线程 A 的局部变量

    shared_ptr n(new Foo); // 线程 B 的局部变量

    C++ shared_ptr的线程安全问题_c++ shared_ptr 线程安全-CSDN博客

    https://www.cnblogs.com/gqtcgq/p/7492772.html

    shared_ptr的API使用

    函数API含义
    p.get()返回p中保存的指针。小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了
    p.use_count()返回与p共享对象的智能指针数量;可能很慢,注意用于调试
    p.unique()若p.use_count()为1,返回true;否则返回false
    p = qp和q都是shared_ptr,所保存的指针能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放
    make_shared(args)返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象
    shared_ptr p(q)p是share_ptr q的拷贝;此操作会递增q中的计数器。q中的指针类型必须能转换为T*
    shared_ptr p(u)p从unique_ptr u那里接管了对象的所有权;将u置为空
    shared_ptr p(q,d)p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用的对象d来代替delete
    shared_ptr p(p2,d)p是share_ptr p2的拷贝;此操作会递增p2中的计数器。p2中的指针类型必须能转换为T*,p将使用可调用的对象d来代替delete
    p.reset()p置为空。若p是唯一指向其对象的shared_ptr,reset会释放此对象
    p.reset(q)传递了参数内置指针q,会令p指向q
    p.reset(q,d)传递了参数d,将会调用d而不是delete来释放q
    p.swap(q)或swap(p,q)交换两个智能指针

    可以通过如下两种方式创建std::shared_ptr对象

    1. auto p = std::shared_ptr(new T);
    2. auto p = std::make_shared(T{});

    shared_ptr的拷贝构造函数会增加引用计数,而移动构造函数不会增加引用计数。

    别名构造函数

    template shared_ptr (const shared_ptr& x, element_type* p) noexcept;  除了存储的指针是p。该对象不拥有 p,也不会管理其存储。相反,它共同拥有 x 的托管对象并算作 x 的一种额外使用。它还将在发布时删除 x 的指针(而不是 p)。它可以用来指向已经被管理的对象的成员。

    why make_shared ? - Bitdewy

    https://www.cnblogs.com/chaohacker/p/14802112.html

    Make_shared

    优点

            效率更高

            异常安全

    缺点

            构造函数是保护或私有时,无法使用 make_shared

            对象的内存可能无法及时回收

    二、unique_ptr

    1、如何创建unique_ptr

    unique_ptr不像shared_ptr一样拥有标准库函数make_shared来创建一个shared_ptr实例。要想创建一个unique_ptr,我们需要将一个new 操作符返回的指针传递给unique_ptr的构造函数。

    2、无法进行复制构造和赋值操作

    unique_ptr没有copy构造函数,不支持普通的拷贝和赋值操作。

    3、可以进行移动构造和移动赋值操作

    unique_ptr虽然没有支持普通的拷贝和赋值操作,但却提供了一种移动机制来将指针的所有权从一个unique_ptr转移给另一个unique_ptr。如果需要转移所有权,可以使用std::move()函数。

    4、可以返回unique_ptr

    unique_ptr不支持拷贝操作,但却有一个例外:可以从函数中返回一个unique_ptr。

    1. int main()
    2. {
    3. // 创建一个unique_ptr实例
    4. unique_ptr<int> pInt(new int(5));
    5. unique_ptr<int> pInt2(pInt); // 报错
    6. unique_ptr<int> pInt3 = pInt; // 报错
    7. }
    8. int main()
    9. {
    10. unique_ptr<int> pInt(new int(5));
    11. unique_ptr<int> pInt2 = std::move(pInt); // 转移所有权
    12. //cout << *pInt << endl; // 出错,pInt为空
    13. cout << *pInt2 << endl;
    14. unique_ptr<int> pInt3(std::move(pInt2));
    15. }
    16. unique_ptr<int> clone(int p)
    17. {
    18. unique_ptr<int> pInt(new int(p));
    19. return pInt; // 返回unique_ptr
    20. }
    21. int main() {
    22. int p = 5;
    23. unique_ptr<int> ret = clone(p);
    24. cout << *ret << endl;
    25. }

     C++ 智能指针与底层实现剖析 - 掘金

    深入理解Modern C++智能指针std::shared_ptr - 知乎

  • 相关阅读:
    【USB电压电流表】基于STM32F103C8T6 for Arduino
    Android Highcharts图表小结之HITooltip一个节点显示多个信息
    基于Splinter演示如何使用Chrome WebDriver
    Nginx01-HTTP简介与Nginx简介(安装、命令介绍、目录介绍、配置文件介绍)
    SpringMVC框架中@Controller类的方法的返回值的详细介绍
    详解 ElasticSearch 基础教程
    Vuex,Vue-router
    C++多线程入门
    memcpy · memmove · memcmp | 使用场景与模拟实现
    富文本编辑器添加图片
  • 原文地址:https://blog.csdn.net/sinat_27652257/article/details/133386862