• C++智能指针剖析(下)boost::shared_ptr&其他


    1. boost::shared_ptr

    前面我已经讲解了两个比较简单的智能指针,它们都有各自的优缺点。由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不了了,如此我们再引入一个智能指针,专门用于处理复制,参数传递的情况,这便是如下的boost::shared_ptr。

    boost::shared_ptr 也属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。在上面我们看到 boost::scoped_ptr 独享所有权,不允许赋值、拷贝,boost::shared_ptr 是专门用于共享所有权的,由于要共享所有权,其在内部使用了引用计数。boost::shared_ptr 也是用于管理单个堆内存对象的。

    这是比较完善的一个智能指针,他是通过指针保持某个对象的共享拥有权的智能指针。若干个shared_ptr对象可以拥有同一个对象,该对象通过维护一个引用计数,记录有多少个shared_ptr指针指向该对象,最后一个指向该对象的shared_ptr被销毁或重置时,即引用计数变为0时,该对象被销毁。销毁对象时使用的是delete表达式或是在构造shared_ptr时传入的自定义删除器(delete),这后面会有详细讲解,但是shared_ptr指针同样拥有缺陷,那就是循环引用,和线程安全问题,这也在后面讲解。先来模拟实现一下shared_ptr指针。

     1 template <class T>
     2 class SharedPtr
     3 {
     4 public:
     5     SharedPtr(T* ptr = NULL)
     6         :_ptr(ptr)
     7         ,_count(new int(0)){
     8         if (_ptr != NULL) {
     9             ++(*_count);
    10         }
    11     }
    12     SharedPtr(const SharedPtr<T>& sp) 
    13         :_ptr(sp._ptr)
    14         ,_count(sp._count){
    15         if (_ptr != NULL) {
    16             ++(*_count);
    17         }
    18     }
    19     SharedPtr<T>& operator=(const SharedPtr<T>& sp) {
    20         if (this != &sp) { //排除对象本身自己给自己赋值
    21             if (--(*_count) <= 0) { 
    22                 delete[]_ptr;
    23                 delete[]_count;
    24             }
    25             else //指向同一个对象的指针互相赋值
    26             { }
    27             _ptr = sp._ptr;
    28             _count = sp._count;
    29             *(_count)++;
    30         }
    31         return *this;
    32     }
    33     ~SharedPtr() {
    34         if (--(*count) == 0) {
    35             delete[] _ptr;
    36             delete[] _count;
    37         }
    38     }
    39     T& operator*(){
    40         return *_ptr;
    41     }
    42     T* operator->() {
    43         return _ptr;
    44     }
    45     bool operator ==(const SharedPtr<T>& sp) {
    46         return (_ptr == sp._ptr);
    47     }
    48     bool operator !=(const SharedPtr<T>& sp) {
    49         return (_ptr != sp._ptr);
    50     }

      51      int UseCount() {
      52         return *_count;
      53     }

    51 private:
    52     T* _ptr;
    53     int* _count;
    54 };

    1.1 问题1:线程安全

    因为使用引用计数值位判定指标,所以在多线程的环境下是不安全的。会因线程调用的先后顺序不同导致错误产生。对于这种问题,解决方法一般是加锁,对引用计数进行加,保证操作是互斥的。

    1.2 问题2:循环引用

    针对循环引用,从实际的例子来大分析问题,以便能更好的理解。看下面代码:

     1 struct ListNode
     2 {
     3     int _data;
     4     SharedPtr<ListNode> _next;
     5     SharedPtr<ListNode> _prev;
     6 
     7     ListNode(int x)
     8         :_data(x), _next(NULL), _prev(NULL)
     9     {}
    10     ~ListNode()
    11     {
    12         cout << "~ListNode()" << endl;
    13     }
    14 };
    15 
    16 void test()
    17 {
    18     SharedPtr<ListNode> A(new ListNode(1));
    19     SharedPtr<ListNode> B(new ListNode(2));
    20 
    21     if (A && B) {
    22         A->_next = B;
    23         B->_prev = A;
    24     }
    25 
    26     cout << "A._count:" << A.UseCount() << endl;
    27     cout << "B._count:" << B.UseCount() << endl;
    28 }
    29 
    30 int main()
    31 {
    32     test();
    33     getchar();
    34     return 0;
    35 }

    输出结果:

    从结果可以看出两个节点的引用计数都是2,且一直没有调用析构函数,这将造成内存泄漏,下面我将图解原理:

    而要解决循环引用的问题,就需要用boost库的另一个智能指针,即boost::weak_ptr。后面在详细讲解。

    1.3 定制删除器(仿函数) 

    经上面分析,我们可以看到,上面的指针不能用于文件的关闭,也不能用于管理mallocnew[]开辟的动态内存的释放,所以我们可以运用仿函数来定制删除器。如下:

     1 template<class T>
     2 struct DeleteArray  //用于new[]开辟的动态内存释放
     3 {
     4     void operator()(T* ptr)
     5     {
     6         cout << "A" << endl;
     7         delete[] ptr;
     8     }
     9 };
    10 struct Fclose  //用于文件关闭
    11 {
    12     void operator()(FILE* ptr)
    13     {
    14         cout << "B" << endl;
    15 
    16         fclose(ptr);
    17     }
    18 };
    19 template<class T>
    20 struct Free     //用于malloc开辟的动态内存的释放
    21 {
    22     void operator()(T* ptr)
    23     {
    24         cout << "C" << endl;
    25         free(ptr);
    26     }
    27 };
    28 int main()
    29 {
    30     shared_ptr<string> ap1(new string[10], DeleteArray<string>());
    31     shared_ptr<FILE> ap2(fopen("test.txt", "w"),Fclose());
    32     shared_ptr<int> ap3((int*)malloc(sizeof(int)), Free<int>());
    33     return 034 }

    2. boost::weak_ptr

     boost::weak_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。

    在讲 boost::weak_ptr 之前,让我们先回顾一下前面讲解的内容。似乎boost::scoped_ptr、boost::shared_ptr 这两个智能指针就可以解决所有单个对象内存的管理了,这儿还多出一个 boost::weak_ptr,是否还有某些情况我们没纳入考虑呢?答案是有。首先 boost::weak_ptr 是专门为 boost::shared_ptr 而准备的。有时候,我们只关心能否使用对象,并不关心内部的引用计数。boost::weak_ptr 是 boost::shared_ptr 的观察者(Observer)对象,观察者意味着 boost::weak_ptr 只对 boost::shared_ptr 进行引用,而不改变其引用计数,当被观察的 boost::shared_ptr 失效后,相应的 boost::weak_ptr 也相应失效。weak_ptr其实是一个辅助性的智能指针,结合shared_ptr指针使用,它的本质就是弱引用,并不增加引用计数值。他没有实现->和*运算符的重载,所以不能直接用它访问对象。针对循环引用这个问题,就是因为不会引起引用计数值的改变,所以我们可以将_next和_prev定义为weak_ptr指针,这样就很好地解决了循环引用的问题。

     1 struct ListNode
     2 {
     3     int _data;
     4     weak_ptr<ListNode> _next;  //定义为weak_ptr指针
     5     weak_ptr<ListNode> _prev;
     6 
     7     ListNode(int x)
     8         :_data(x), _next(NULL), _prev(NULL)
     9     {}
    10     ~ListNode()
    11     {
    12         cout << "~ListNode()" << endl;
    13     }
    14 };

    其实 boost::weak_ptr 主要用在软件架构设计中,可以在基类(此处的基类并非抽象基类,而是指继承于抽象基类的虚基类)中定义一个 boost::weak_ptr,用于指向子类的boost::shared_ptr,这样基类仅仅观察自己的 boost::weak_ptr 是否为空就知道子类有没对自己赋值了,而不用影响子类 boost::shared_ptr 的引用计数,用以降低复杂度,更好的管理对象。

    boost库剩余的两个指针:auto_arrshared_arr.这两个都是管理数组的,因为之前几个指针的析构函数中都是delete,不能对数组进行释放,所以我们自己定制删除器,而这两个指针就是专门管理指针的。下面也来模拟实现一下。

    3. 模拟实现auto_arr

     1 template<class T>
     2 class AutoArr
     3 {
     4 public:
     5     AutoArr(T* ptr = NULL)
     6         :_ptr(ptr)
     7     {}
     8 
     9     ~AutoArr()
    10     {
    11         delete[] _ptr;
    12     }
    13 
    14     AutoArr(const AutoArr<T>& s)
    15     {
    16         _ptr = s._ptr;
    17         s._ptr = NULL;
    18     }
    19 
    20     AutoArr<T>& operator=(const AutoArr<T>& s)
    21     {
    22         if (this != &s)
    23         {
    24             _ptr = s._ptr;
    25             s._ptr = NULL;
    26         }
    27         return *this;
    28     }
    29 
    30     T& operator[](size_t pos)
    31     {
    32         if (_ptr == NULL)
    33         {
    34             throw a;
    35         }
    36             return *(_ptr+pos);
    37     }
    38 
    39     void set(T* ptr)
    40     {
    41         int i = 0;
    42         while (*(ptr + i))
    43         {
    44             *(_ptr + i) = *(ptr + i);
    45             i++;
    46         }
    47     }
    48 
    49 protected:
    50     T* ptr;
    51 };

    4. 模拟实现shared_arr

     1 template<class T>
     2 
     3 class SharedArr
     4 {
     5 public:
     6     SharedArr(T* ptr = NULL)
     7         :_ptr(ptr),_count(new int(0))
     8     {
     9         (*_count)++;
    10     }
    11 
    12     ~SharedArr()
    13     {
    14         delete[] _ptr;
    15     }
    16 
    17     SharedArr(const SharedArr<T>& s)
    18     {
    19         _ptr = s._ptr;
    20         (*_count)++;
    21     }
    22 
    23     SharedArr<T>& operator=(const SharedArr<T>& s)
    24     {
    25         if (this != &s)
    26         {
    27             if (--(*_count) <= 0)
    28             {
    29                 delete _ptr;
    30                 delete _count;
    31             }
    32             else
    33             { }
    34             _ptr = s._ptr;
    35             _count = s._count;
    36             (*_count)++;
    37         }
    38     }
    39 
    40     T& operator[](size_t pos)
    41     {
    42         if (_ptr == NULL)
    43         {
    44             throw 1;
    45         }
    46         return *(_ptr + pos);
    47     }
    48 
    49 protected:
    50     T* _ptr;
    51     int* _count;
    52 };

    5. 如何选择智能指针?

    在掌握了这几种智能指针后,大家可能会想另一个问题:在实际应用中,应使用哪种智能指针呢?下面给出几个使用指南。

    (1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:

    • 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
    • 两个对象都包含指向第三个对象的指针;
    • STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。

    (2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。例如,可在程序中使用类似于下面的代码段。

     1 unique_ptr<int> make_int(int n)
     2 {
     3     return unique_ptr<int>(new int(n));
     4 }
     5 void show(unique_ptr<int> &p1)
     6 {
     7     cout << *a << ' ';
     8 }
     9 int main()
    10 {
    11     ...
    12     vector<unique_ptr<int> > vp(size);
    13     for(int i = 0; i < vp.size(); i++)
    14         vp[i] = make_int(rand() % 1000);              // copy temporary unique_ptr
    15     vp.push_back(make_int(rand() % 1000));     // ok because arg is temporary
    16     for_each(vp.begin(), vp.end(), show);           // use for_each()
    17     ...
    18 }

    其中push_back调用没有问题,因为它返回一个临时unique_ptr,该unique_ptr被赋给vp中的一个unique_ptr。另外,如果按值而不是按引用给show()传递对象,for_each()将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这是不允许的。前面说过,编译器将发现错误使用unique_ptr的企图。

    在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()的返回类型为unique_ptr<int>:

    1 unique_ptr<int> pup(make_int(rand() % 1000));   // ok
    2 shared_ptr<int> spp(pup);                       // not allowed, pup as lvalue
    3 shared_ptr<int> spr(make_int(rand() % 1000));   // ok

    模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。

    在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。如果你的编译器没有unique_ptr,可考虑使用Boost库提供的scoped_ptr,它与unique_ptr类似。

  • 相关阅读:
    【Python】【解决】UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 1: ordinal not in range(128)
    【小技巧】强制重启无线网卡,解决“区域中找不到无线网络,请确定您计算机上的无线开关已启用”问题
    【小技巧】9针USB转串口简易连通性测试,附25针转9针
    【ACM】HDU1008 Elevator 新手题前后不同的代码版本
    【Android】命令行jarsigner签字和解决找不到证书链错误
    LeetCode 【47. Permutations II】
    LeetCode 【46. Permutations】
    Python asyncio库的学习和使用
    LeetCode 【190. Reverse Bits】
    LeetCode 【21. Merge Two Sorted Lists】
  • 原文地址:https://www.cnblogs.com/33debug/p/6838503.html
Copyright © 2020-2023  润新知