zoukankan      html  css  js  c++  java
  • [转]c++智能指针 shared_ptr

    [转自 https://www.cnblogs.com/diysoul/p/5930361.html]

      shared_ptr 是一个标准的共享所有权的智能指针, 允许多个指针指向同一个对象. 定义在 memory 文件中(非memory.h), 命名空间为 std.

      shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针, 当然这需要额外的开销:
      (1) shared_ptr 对象除了包括一个所拥有对象的指针外, 还必须包括一个引用计数代理对象的指针.
      (2) 时间上的开销主要在初始化和拷贝操作上, *和->操作符重载的开销跟auto_ptr是一样.
      (3) 开销并不是我们不使用shared_ptr的理由, 永远不要进行不成熟的优化, 直到性能分析器告诉你这一点.

    使用方法

    可以使用模板函数 make_shared 创建对象, make_shared 需指定类型('<>'中)及参数('()'内), 传递的参数必须与指定的类型的构造函数匹配. 如:
      std::shared_ptr<int> sp1 = std::make_shared<int>(10);
      std::shared_ptr<std::string> sp2 = std::make_shared<std::string>("Hello c++");

    也可以定义 auto 类型的变量来保存 make_shared 的结果.
      auto sp3 = std::make_shared<int>(11);
      printf("sp3=%d ", *sp3);
      auto sp4 = std::make_shared<std::string>("C++11");
      printf("sp4=%s ", (*sp4).c_str());

    成员函数

    use_count 返回引用计数的个数
    unique 返回是否是独占所有权( use_count 为 1)
    swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
    reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
    get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr<int> sp(new int(1)); sp 与 sp.get()是等价的

    以下代码演示各个函数的用法与特点:

     1 std::shared_ptr<int> sp0(new int(2));
     2             std::shared_ptr<int> sp1(new int(11));
     3             std::shared_ptr<int> sp2 = sp1;
     4             printf("%d
    ", *sp0);               // 2
     5             printf("%d
    ", *sp1);               // 11
     6             printf("%d
    ", *sp2);               // 11
     7             sp1.swap(sp0);
     8             printf("%d
    ", *sp0);               // 11
     9             printf("%d
    ", *sp1);               // 2
    10             printf("%d
    ", *sp2);               // 11
    11 
    12             std::shared_ptr<int> sp3(new int(22));
    13             std::shared_ptr<int> sp4 = sp3;
    14             printf("%d
    ", *sp3);               // 22
    15             printf("%d
    ", *sp4);               // 22
    16             sp3.reset();                        
    17             printf("%d
    ", sp3.use_count());    // 0
    18             printf("%d
    ", sp4.use_count());    // 1
    19             printf("%d
    ", sp3);                // 0
    20             printf("%d
    ", sp4);                // 指向所拥有对象的地址
    21             
    22             std::shared_ptr<int> sp5(new int(22));
    23             std::shared_ptr<int> sp6 = sp5;
    24             std::shared_ptr<int> sp7 = sp5;
    25             printf("%d
    ", *sp5);               // 22
    26             printf("%d
    ", *sp6);               // 22
    27             printf("%d
    ", *sp7);               // 22
    28             printf("%d
    ", sp5.use_count());    // 3
    29             printf("%d
    ", sp6.use_count());    // 3
    30             printf("%d
    ", sp7.use_count());    // 3
    31             sp5.reset(new int(33));                        
    32             printf("%d
    ", sp5.use_count());    // 1
    33             printf("%d
    ", sp6.use_count());    // 2
    34             printf("%d
    ", sp7.use_count());    // 2
    35             printf("%d
    ", *sp5);               // 33
    36             printf("%d
    ", *sp6);               // 22
    37             printf("%d
    ", *sp7);               // 2

    shared_ptr 的赋值构造函数和拷贝构造函数:
      auto r = std::make_shared<int>(); // r 的指向的对象只有一个引用, 其 use_count == 1
      auto q = r; (或auto q(r);) // 给 r 赋值, 令其指向另一个地址, q 原来指向的对象的引用计数减1(如果为0, 释放内存), r指向的对象的引用计数加1, 此时 q 与 r 指向同一个对象, 并且其引用计数相同, 都为原来的值加1.
    以下面的代码测试:

     1             std::shared_ptr<int> sp1 = std::make_shared<int>(10);
     2             std::shared_ptr<int> sp2 = std::make_shared<int>(11);
     3             auto sp3 = sp2; 或 auto sp3(sp2);
     4             printf("sp1.use_count = %d
    ", sp1.use_count());  // 1
     5             printf("sp2.use_count = %d
    ", sp2.use_count());  // 2
     6             printf("sp3.use_count = %d
    ", sp3.use_count());  // 2
     7             sp3 = sp1;
     8             printf("sp1.use_count = %d
    ", sp1.use_count());  // 2
     9             printf("sp2.use_count = %d
    ", sp2.use_count());  // 1
    10             printf("sp3.use_count = %d
    ", sp3.use_count());  // 2

    何时需要使用 shared_ptr ?

    (1) 程序不知道自己需要使用多少对象. 如使用窗口类, 使用 shared_ptr 为了让多个对象能共享相同的底层数据.

    1 std::vector<std::string> v1; // 一个空的 vector
    2             // 在某个新的作用域中拷贝数据到 v1 中
    3             {
    4                 std::vector<std::string> v2;
    5                 v2.push_back("a");
    6                 v2.push_back("b");
    7                 v2.push_back("c");
    8                 v1 = v2;
    9             } // 作用域结束时 v2 被销毁, 数据被拷贝到 v1 中

    (2) 程序不知道所需对象的准确类型.
    (3) 程序需要在多个对象间共享数据.

    自定义释放器(函数)

      自定义释放器(函数), 它能完成对 shared_ptr 中保存的指针进行释放操作, 还能处理 shared_ptr 的内部对象未完成的部分工作.

      假设如下是一个连接管理类, 此类由于历史原因, 无法在析构函数中进行断开连接, 此时用自定义的释放器可以很好的完成此工作:

     1 class CConnnect
     2         {
     3             void Disconnect() { PRINT_FUN(); }
     4         };
     5 
     6         void Deleter(CConnnect* obj)
     7         {
     8             obj->Disconnect(); // 做其它释放或断开连接等工作
     9             delete obj; // 删除对象指针
    10         }
    11         
    12         std::shared_ptr<CConnnect> sps(new CConnnect, Deleter);

    使用 shared_ptr 的注意事项

    (1) shared_ptr 作为被保护的对象的成员时, 小心因循环引用造成无法释放资源.
      假设 a 对象中含有一个 shared_ptr<CB> 指向 b 对象, b 对象中含有一个 shared_ptr<CA> 指向 a 对象, 并且 a, b 对象都是堆中分配的。
      考虑对象 b 中的 m_spa 是我们能最后一个看到 a 对象的共享智能指针, 其 use_count 为2, 因为对象 b 中持有 a 的指针, 所以当 m_spa 说再见时, m_spa 只是把 a 对象的 use_count 改成1; 对象 a 同理; 然后就失去了 a,b 对象的联系.
      解决此方法是使用 weak_ptr 替换 shared_ptr . 以下为错误用法, 导致相互引用, 最后无法释放对象

     1 class CB;
     2             class CA;
     3 
     4             class CA
     5             {
     6             public:
     7                 CA(){}
     8                 ~CA(){PRINT_FUN();}
     9 
    10                 void Register(const std::shared_ptr<CB>& sp)
    11                 {
    12                     m_sp = sp;
    13                 }
    14 
    15             private:
    16                 std::shared_ptr<CB> m_sp;
    17             };
    18 
    19             class CB
    20             {
    21             public:
    22                 CB(){};
    23                 ~CB(){PRINT_FUN();};
    24 
    25                 void Register(const std::shared_ptr<CA>& sp)
    26                 {
    27                     m_sp = sp;
    28                 }
    29 
    30             private:
    31                 std::shared_ptr<CA> m_sp;
    32             };
    33 
    34             std::shared_ptr<CA> spa(new CA);
    35             std::shared_ptr<CB> spb(new CB);
    36 
    37             spb->Register(spa);
    38             spa->Register(spb);
    39             printf("%d
    ", spb.use_count()); // 2
    40             printf("%d
    ", spa.use_count()); // 2

    运行上述代码会发现 CA, CB 析构函数都不会打印. 因为他们都没有释放内存.

    (2) 小心对象内部生成 shared_ptr ??

    1 class Y : public std::enable_shared_from_this<Y>
    2             {
    3             public:
    4                 std::shared_ptr<Y> GetSharePtr()
    5                 {
    6                     return shared_from_this();
    7                 }
    8             };

    对普通的类(没有继承 enable_shared_from_this) T 的 shared_ptr<T> p(new T). p 作为栈对象占8个字节,为了记录( new T )对象的引用计数, p 会在堆上分配 16 个字节以保存引用计数等“智能信息”.
        share_ptr 没有“嵌入(intrusive)”到T对象, 或者说T对象对 share_ptr 毫不知情.
        而 Y 对象则不同, Y 对象已经被“嵌入”了一些 share_ptr 相关的信息, 目的是为了找到“全局性”的那16字节的本对象的“智能信息”.

    复制代码
            考虑下面的代码:
                Y y;
                std::shared_ptr<Y> spy = y.GetSharePtr(); // 错误, y 根本不是 new 创建的
                Y* y = new Y;
                std::shared_ptr<Y> spy = y->GetSharePtr(); // 错误, 问题依旧存在, 程序直接崩溃
            正确用法:
                std::shared_ptr<Y> spy(new Y);
                std::shared_ptr<Y> p = spy->GetSharePtr();
                printf("%d
    ", p.use_count()); // 2
    复制代码

    (3) 小心多线程对引用计数的影响

    首先, 如果是轻量级的锁, 比如 InterLockIncrement 等, 对程序影响不大; 如果是重量级的锁, 就要考虑因为 share_ptr 维护引用计数而造成的上下文切换开销.
    其次, 多线程同时对 shared_ptr 读写时, 行为不确定, 因为shared_ptr本身有两个成员px,pi. 多线程同时对 px 读写要出问题, 与一个 int 的全局变量多线程读写会出问题的原因一样.

    (4) 与 weak_ptr 一起工作时, weak_ptr 在使用前需要检查合法性

    复制代码
            std::weak_ptr<A> wp;
            {
                std::shared_ptr<A>  sp(new A);  //sp.use_count()==1
                wp = sp; //wp不会改变引用计数,所以sp.use_count()==1
                std::shared_ptr<A> sp2 = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
            }
            printf("expired:%d
    ", wp.expired()); // 1
            std::shared_ptr<A> sp_null = wp.lock(); //sp_null .use_count()==0;
    复制代码

    上述代码中 sp 和 sp2 离开了作用域, 其容纳的对象已经被释放了. 得到了一个容纳 NULL 指针的 sp_null 对象.
    在使用 wp 前需要调用 wp.expired() 函数判断一下. 因为 wp 还仍旧存在, 虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息.
    直到最后一个 weak_ptr 对象被析构, 这块“堆”存储块才能被回收, 否则 weak_ptr 无法知道自己所容纳的那个指针资源的当前状态.

    (5) shared_ptr 不支持数组, 如果使用数组, 需要自定义删除器, 如下是一个利用 lambda 实现的删除器:

          std::shared_ptr<int> sps(new int[10], [](int *p){delete[] p;});

      对于数组元素的访问, 需使要使用 get 方法取得内部元素的地址后, 再加上偏移量取得.

    复制代码
                for (size_t i = 0; i < 10; i++)
                {
                    *((int*)sps.get() + i) = 10 - i;
                }
    
                for (size_t i = 0; i < 10; i++)
                {
                    printf("%d -- %d
    ", i, *((int*)sps.get() + i));
                }
    复制代码
  • 相关阅读:
    centos7.6 使用yum安装mysql5.7
    解决hadoop本地库问题
    docker-compose 启动警告
    docker 安装zabbix5.0 界面乱码问题解决
    docker 部署zabbix问题
    zookeeper 超时问题
    hbase regionserver异常宕机
    (转载)hadoop 滚动升级
    hadoop Requested data length 86483783 is longer than maximum configured RPC length
    zkfc 异常退出问题,报错Received stat error from Zookeeper. code:CONNECTIONLOSS
  • 原文地址:https://www.cnblogs.com/yi-mu-xi/p/9900577.html
Copyright © 2011-2022 走看看