C++ 智能指针

打印 上一主题 下一主题

主题 657|帖子 657|积分 1971

一、为什么必要智能指针

看如下代码有什么问题:
  1. int div()
  2. {
  3.         int a, b;
  4.         cin >> a >> b;
  5.         if (b == 0)
  6.                 throw invalid_argument("除0错误");
  7.         return a / b;
  8. }
  9. void Func()
  10. {
  11.         // 1、如果p1这里new 抛异常会如何?
  12.         // 2、如果p2这里new 抛异常会如何?
  13.         // 3、如果div调用这里又会抛异常会如何?
  14.         int* p1 = new int;
  15.         int* p2 = new int;
  16.         cout << div() << endl;
  17.         delete p1;
  18.         delete p2;
  19. }
  20. int main()
  21. {
  22.         try
  23.         {
  24.                 Func();
  25.         }
  26.         catch (exception& e)
  27.         {
  28.                 cout << e.what() << endl;
  29.         }
  30.         return 0;
  31. }
复制代码
假如div发生除零错误抛异常的话,此时p1 p2已经开发好了,抛异常的话会跳转到catch语句而不会执行delete语句了,就会造成内存泄漏。
假如p2申请空间抛异常的话,p1肯定是已经开发好的,末了的delete p1语句就执行不了了,也会造成内存泄漏,虽然我们也可以通过抛异常的方式办理,但是假如开发的空间比力多的话,代码就会一层套一层,不是很美观
  1. void Func()
  2. {
  3.         // 1、如果p1这里new 抛异常会如何?
  4.         // 2、如果p2这里new 抛异常会如何?
  5.         // 3、如果div调用这里又会抛异常会如何?
  6.         int* p1 = new int;
  7.         int* p2 = nullptr;
  8.         try
  9.         {
  10.                 p2 = new int;
  11.                 try
  12.                 {
  13.                         cout << div() << endl;
  14.                 }
  15.                 catch (...)
  16.                 {
  17.                         delete p1;
  18.                         delete p2;
  19.                 }
  20.         }
  21.         catch (...)
  22.         {
  23.                 delete p1;
  24.         }
  25.         delete p1;
  26.         delete p2;
  27. }
复制代码
为相识决上述问题就引入了智能指针
二、智能指针的利用及原理

2.1 RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简朴技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有用,末了在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做 法有两大利益:


  • 不必要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有用。
接下来我们自己简朴模拟实现一下RAII的智能指针
  1. // 使用RAII思想设计的SmartPtr类
  2. template<class T>
  3. class SmartPtr {
  4. public:
  5.    SmartPtr(T* ptr = nullptr)
  6.       : _ptr(ptr)
  7.   {}
  8.    ~SmartPtr()
  9.   {
  10.        if(_ptr)
  11.            delete _ptr;
  12.   }
  13.    
  14. private:
  15.    T* _ptr;
  16. };
  17. int div()
  18. {
  19.   int a, b;
  20.   cin >> a >> b;
  21.   if (b == 0)
  22.   throw invalid_argument("除0错误");
  23.   return a / b;
  24. }
  25. void Func()
  26. {
  27.   ShardPtr<int> sp1(new int); //智能指针
  28.   ShardPtr<int> sp2(new int);//智能指针
  29.   cout << div() << endl;
  30. }
  31. int main()
  32. {
  33.    try {
  34.      Func();
  35.   }
  36.    catch(const exception& e)
  37.   {
  38.        cout<<e.what()<<endl;
  39.   }
  40. return 0;
  41. }
复制代码
通过智能指针管理p1和p2,此时岂论声请p2抛异常还是除零抛异常,跳转到catch语句后,sp1和sp2都会由于出了作用而调用析构函数释放资源,就不会发生内存泄露了,但是上述代码存在一些问题,当两个智能指针指向同一份资源时,就会释放两次资源而崩溃,接下来我们看看库里的智能指针是如何办理这个问题的。
2.2 std:: auto_ptr

auto_ptr是C++98提出来的,它办理这个问题的思路是管理权转移的思想,当另一个智能指针指向资源后,就将上一个智能指针置为空,始终保持只有一个智能指针管理一个资源
  1. // C++98 管理权转移 auto_ptr
  2. namespace zyq
  3. {
  4.         template<class T>
  5.         class auto_ptr
  6.         {
  7.         public:
  8.                 auto_ptr(T* ptr)
  9.                         :_ptr(ptr)
  10.                 {}
  11.                 auto_ptr(auto_ptr<T>& sp)
  12.                         :_ptr(sp._ptr)
  13.                 {
  14.                         // 管理权转移
  15.                         sp._ptr = nullptr;
  16.                 }
  17.                 auto_ptr<T>& operator=(auto_ptr<T>& ap)
  18.                 {
  19.                         // 检测是否为自己给自己赋值
  20.                         if (this != &ap)
  21.                         {
  22.                                 // 释放当前对象中资源
  23.                                 if (_ptr)
  24.                                         delete _ptr;
  25.                                 // 转移ap中资源到当前对象中
  26.                                 _ptr = ap._ptr;
  27.                                 ap._ptr = NULL;
  28.                         }
  29.                         return *this;
  30.                 }
  31.                 ~auto_ptr()
  32.                 {
  33.                         if (_ptr)
  34.                         {
  35.                                 cout << "delete:" << _ptr << endl;
  36.                                 delete _ptr;
  37.                         }
  38.                 }
  39.                 // 像指针一样使用
  40.                 T& operator*()
  41.                 {
  42.                         return *_ptr;
  43.                 }
  44.                 T* operator->()
  45.                 {
  46.                         return _ptr;
  47.                 }
  48.         private:
  49.                 T* _ptr;
  50.         };
  51. }
复制代码
但是auto_ptr的计划并不是很好,由于会导致原来的智能指针发生悬空问题,当我们再次利用原来的智能指针可能会发生访问空指针的问题,所以很多公司都克制利用auto_ptr
  1. int main()
  2. {
  3. std::auto_ptr<int> sp1(new int);
  4. std::auto_ptr<int> sp2(sp1); // 管理权转移
  5. // sp1悬空
  6. *sp2 = 10;
  7. cout << *sp2 << endl;
  8. cout << *sp1 << endl;
  9. return 0;
  10. }
复制代码
2.3 std::unique_ptr

C++11中开始提供更靠谱的unique_ptr,它的思想很简朴,直接将拷贝构造和赋值克制掉,不让拷贝


  • C++11库才更新智能指针实现
  • C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr
  • C++11将boost库中智能指针英华部分吸收了过来
  • C++11->unique_ptr/shared_ptr/weak_ptr
  1. // unique_ptr/scoped_ptr
  2. // 原理:简单粗暴 -- 防拷贝
  3. namespace bit
  4. {
  5.         template<class T>
  6.         class unique_ptr
  7.         {
  8.         public:
  9.                 unique_ptr(T* ptr)
  10.                         :_ptr(ptr)
  11.                 {}
  12.                 ~unique_ptr()
  13.                 {
  14.                         if (_ptr)
  15.                         {
  16.                                 cout << "delete:" << _ptr << endl;
  17.                                 delete _ptr;
  18.                         }
  19.                 }
  20.                 // 像指针一样使用
  21.                 T& operator*()
  22.                 {
  23.                         return *_ptr;
  24.                 }
  25.                 T* operator->()
  26.                 {
  27.                         return _ptr;
  28.                 }
  29.                 unique_ptr(const unique_ptr<T>&sp) = delete;
  30.                 unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;
  31.         private:
  32.                 T* _ptr;
  33.         };
  34. }
复制代码
2.4 std::shared_ptr

C++11中也提供了更加靠谱的shared_ptr,它与unique_ptr的差别点是他可以拷贝,那他是如何办理原来的问题的呢?它是通过引用计数的方式来办理释放两次资源的问题的,当发生拷贝时,会让这个资源的引用计数加1,当一个智能指针调用析构函数时,起首会让引用计数减1,假如此时引用计数为0的话表示没有智能指针指向这个资源了,就会释放掉这个资源,假如引用计数不为0的话,表示还有智能指针指向这个资源,此时就不会释放掉资源了。
模拟实现:
  由于引用计数涉及++ --的操作,在多线程的情况下并不安全,所以我们必要通过肯定的手段让他变得线程安全
方法一:互斥锁
  1. namespace zyq
  2. {
  3.         template<class T>
  4.         class shared_ptr
  5.         {
  6.         public:
  7.                 //构造
  8.                 shared_ptr(T* ptr)
  9.                         :_ptr(ptr),
  10.                         _numptr(new int(1)),
  11.                         _pmtx(new mutex)
  12.                 {}
  13.                 shared_ptr()
  14.                 {}
  15.                 ~shared_ptr()
  16.                 {
  17.                         release();
  18.                 }
  19.                 void AddRef()
  20.                 {
  21.                         _pmtx->lock();
  22.                         ++(*_numptr);
  23.                         _pmtx->unlock();
  24.                 }
  25.                 void release()
  26.                 {
  27.                         _pmtx->lock();
  28.                         bool flag = false;
  29.                         if (--(*_numptr) == 0)
  30.                         {
  31.                                 cout << "releasse" << endl;
  32.                                 delete _ptr;
  33.                                 delete _numptr;
  34.                                 flag = true;
  35.                         }
  36.                         _pmtx->unlock();
  37.                         if (flag == true)
  38.                         {
  39.                                 delete _pmtx;
  40.                         }
  41.                 }
  42.                 //拷贝构造
  43.                 shared_ptr(const shared_ptr<T>& P)
  44.                 {
  45.                         _ptr = P._ptr;
  46.                         _numptr = P._numptr;
  47.                         _pmtx = P._pmtx;
  48.                         AddRef();
  49.                 }
  50.                 shared_ptr<T>& operator=(const shared_ptr<T>& P)
  51.                 {
  52.                         //自己给自己赋值有两种情况 sp1=sp1   sp2=sp3  sp2 sp3指向同一资源
  53.                         if (_ptr != P._ptr)
  54.                         {
  55.                                 release();
  56.                                 _ptr = P._ptr;
  57.                                 _numptr = P._numptr;
  58.                                 _pmtx = P._pmtx;
  59.                                 AddRef();
  60.                         }
  61.                         return *this;
  62.                 }
  63.                 T& operator*()
  64.                 {
  65.                         return *_ptr;
  66.                 }
  67.                 T* operator->()
  68.                 {
  69.                         return _ptr;
  70.                 }
  71.                 int use_count()
  72.                 {
  73.                         return *_numptr;
  74.                 }
  75.         private:
  76.                 T* _ptr;
  77.                 int* _numptr;
  78.                 mutex*  _pmtx;
  79.         };
  80. }
复制代码
方法二:原子操作
  1. namespace zyq
  2. {
  3.         template<class T>
  4.         class shared_ptr
  5.         {
  6.         public:
  7.                 //构造
  8.                 shared_ptr(T* ptr)
  9.                         :_ptr(ptr),
  10.                         _numptr(new atomic<int>(1))
  11.                 {}
  12.                 shared_ptr()
  13.                 {}
  14.                 ~shared_ptr()
  15.                 {
  16.                         release();
  17.                 }
  18.                 void release()
  19.                 {
  20.                         if (--(*_numptr) == 0)
  21.                         {
  22.                                 cout << "releasse" << endl;
  23.                                 delete _ptr;
  24.                                 delete _numptr;
  25.                         }
  26.                 }
  27.                 //拷贝构造
  28.                 shared_ptr(const shared_ptr<T>& P)
  29.                 {
  30.                         _ptr = P._ptr;
  31.                         _numptr = P._numptr;
  32.                         (*_numptr)++;
  33.                 }
  34.                 shared_ptr<T>& operator=(const shared_ptr<T>& P)
  35.                 {
  36.                         //自己给自己赋值有两种情况 sp1=sp1   sp2=sp3  sp2 sp3指向同一资源
  37.                         if (_ptr != P._ptr)
  38.                         {
  39.                                 release();
  40.                                 _ptr = P._ptr;
  41.                                 _numptr = P._numptr;
  42.                                 (*_numptr)++;
  43.                         }
  44.                         return *this;
  45.                 }
  46.                 T& operator*()
  47.                 {
  48.                         return *_ptr;
  49.                 }
  50.                 T* operator->()
  51.                 {
  52.                         return _ptr;
  53.                 }
  54.                 int use_count()
  55.                 {
  56.                         return *_numptr;
  57.                 }
  58.         private:
  59.                 T* _ptr;
  60.                 atomic<int>* _numptr;
  61.         };
复制代码
shared_ptr的缺点:循环引用
当发生雷同如下的情况时,shared_ptr就会有点问题
  1. struct ListNode
  2. {
  3.    int _data;
  4.    shared_ptr<ListNode> _prev;
  5.    shared_ptr<ListNode> _next;
  6.    ~ListNode(){ cout << "~ListNode()" << endl; }
  7. };
  8. int main()
  9. {
  10.    shared_ptr<ListNode> node1(new ListNode);
  11.    shared_ptr<ListNode> node2(new ListNode);
  12.    cout << node1.use_count() << endl;  //1
  13.    cout << node2.use_count() << endl;  //1
  14.    node1->_next = node2;
  15.    node2->_prev = node1;
  16.    cout << node1.use_count() << endl;  //2
  17.    cout << node2.use_count() << endl;  //2
  18.    return 0;
  19. }
  20. //当程序结束是两个智能指针的引用计数应该都为0,但是这种情况 两个人的引用计数都为1
复制代码
问题分析:

我们假设node1指针的资源为节点1,node2指针的资源为节点2,当执行到return语句时,node2和node1会起首调用自己析构函数,此时两个节点引用计数--,变为了1,假如想让节点1的引用计数变为0的话就必要让node2的prev析构,那node2的prev什么时候析构呢?node2的prev是节点2管着的,当节点2释放prev就会释放,那节点2什么时候释放呢?当node1的next析构,节点2就会释放,而node1的next只有当节点1释放才会释放,如许节点1与节点2互相影响,就会造成循环引用的问题。为相识决这个问题,C++11也引入了weak_ptr, 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和 _prev不会增加node1和node2的引用计数。
2.5 剩余问题

假如不是new出来的对象如何通过智能指针管理呢?由于库的底层释放资源都是delete ptr实现的,假如我们用malloc申请资源怎么办呢?大概我们申请的类型显示界说出了析构函数而且我们申请了多个对象怎么办理呢?
其实shared_ptr计划了一个删除器来办理这个问题,我们可以在创建智能指针时将释放资源的方法也传进去,如许就办理了
  1. // 仿函数的删除器
  2. template<class T>
  3. struct FreeFunc {
  4.      void operator()(T* ptr)
  5.      {
  6.      cout << "free:" << ptr << endl;
  7.      free(ptr);
  8.      }
  9. };
  10. template<class T>
  11. struct DeleteArrayFunc {
  12.      void operator()(T* ptr)
  13.      {
  14.      cout << "delete[]" << ptr << endl;
  15.      delete[] ptr;
  16.      }
  17. };
  18. int main()
  19. {
  20.      FreeFunc<int> freeFunc;
  21.      std::shared_ptr<int> sp1((int*)malloc(4), freeFunc);
  22.      DeleteArrayFunc<int> deleteArrayFunc;
  23.      std::shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
  24.    
  25.      std::shared_ptr<A> sp4(new A[10], [](A* p){delete[] p; });
  26.      std::shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE* p)
  27.      {
  28.          fclose(p);
  29.      });
  30.      return 0;
  31. }
复制代码
接下来我们修改一下我们自己模拟实现的shared_ptr
  1. namespace zyq
  2. {
  3.         template<class T>
  4.         class shared_ptr
  5.         {
  6.         public:
  7.                 //构造
  8.                 template<class D>
  9.                 shared_ptr(T* ptr,D del)
  10.                         :_ptr(ptr),
  11.                         _numptr(new atomic<int>(1)),
  12.                         _del(del)
  13.                 {}
  14.                 shared_ptr()
  15.                 {}
  16.                 ~shared_ptr()
  17.                 {
  18.                         release();
  19.                 }
  20.                 void release()
  21.                 {
  22.                         if (--(*_numptr) == 0)
  23.                         {
  24.                                 cout << "release" << endl;
  25.                                 _del(_ptr);
  26.                                 delete _numptr;
  27.                         }
  28.                 }
  29.                 //拷贝构造
  30.                 shared_ptr(const shared_ptr<T>& P)
  31.                 {
  32.                         _ptr = P._ptr;
  33.                         _numptr = P._numptr;
  34.                         (*_numptr)++;
  35.                 }
  36.                 shared_ptr<T>& operator=(const shared_ptr<T>& P)
  37.                 {
  38.                         //自己给自己赋值有两种情况 sp1=sp1   sp2=sp3  sp2 sp3指向同一资源
  39.                         if (_ptr != P._ptr)
  40.                         {
  41.                                 release();
  42.                                 _ptr = P._ptr;
  43.                                 _numptr = P._numptr;
  44.                                 (*_numptr)++;
  45.                         }
  46.                         return *this;
  47.                 }
  48.                 T& operator*()
  49.                 {
  50.                         return *_ptr;
  51.                 }
  52.                 T* operator->()
  53.                 {
  54.                         return _ptr;
  55.                 }
  56.                 int use_count()
  57.                 {
  58.                         return *_numptr;
  59.                 }
  60.         private:
  61.                 T* _ptr;
  62.                 atomic<int>* _numptr;
  63.                 function<void(T*)> _del = [](T* ptr) {delete ptr; }; //不传定制删除器就采用默认的方式
  64.         };
  65. }
  66. int main()
  67. {
  68.         shared_ptr<A> sp1(new A[5], [](A* ptr) {delete[] ptr; });
  69.         shared_ptr<A> sp2((A*)malloc(sizeof(A)*5), [](A* ptr){free( ptr); });
  70. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

李优秀

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表