【C++】智能指针

为什么需要智能指针?

分析代码:

 double div()
 {
     int a, b;
     cin >> a >> b;
     if (b == 0)
         throw invalid_argument("除0错误");
     return (double)a / (double)b;
 }
 void func()
 {
     int* p1 = new int;
     int* p2 = new int;
     //如果p2在申请空间时抛异常,可能导致内存泄漏。
     cout << div() << endl;//如果这里出现了异常,就会跳转至main函数中捕捉异常,可能会导致内存泄露
     delete p1;
     delete p2;
 }
 int main()
 {
     try
     {
         func();
     }
     catch (exception& e)
     {
         cout << e.what() << endl;
     }
     return 0;
 }

内存泄漏

概念:内存泄漏是因为疏忽或者错误造成程序未能释放已经不再使用的内存的情况。内存泄漏指的是由于设计失误,系统失去了对这段内存的控制,导致了内存的浪费。

内存泄露的危害:长期运行的程序出现内存刺蛾楼,可能导致响越来越慢,最终卡死。 示例:

 
void test()
 {
     int* p = (int*)malloc(400);
     int* p2 = new int;
     func();//如果func函数抛异常,会导致p/p2的空间无法被释放,造成内存泄漏
     free(p);
     delete p2;
 }

内存泄漏分类

堆内存泄漏 系统内存泄露

避免内存泄漏

1.申请的空间必须记得释放。 2.采用RAII思想或者智能指针来管理资源。 3.采用内存泄漏工具来检测。

智能指针的使用及原理

RAII

RAII(Rescoure Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源的技术。 在对象构造时获取资源,接着控制对资源的访问使之在对象生命周期内始终保持有效,最后在对象析构的时候释放资源。实际是将管理资源的责任分给了一个对象。 这种做法有两大好处; 1.不需要显示的释放资源。 2.对象所需要的资源在其生命周期内始终保持有效。

 
//使用RAII思想设计的SmartPtr类
 template<class T>
 class SmartPtr
 {
 public:
     SmartPtr(T* ptr)
         :_ptr(ptr)
     {}
     ~SmartPtr()
     {
         if (_ptr)
             delete  _ptr;
     }
 private:
     T* _ptr;
 };
 double div()
 {
     int a, b;
     cin >> a >> b;
     if (b == 0)
         throw invalid_argument("除0错误");
     return (double)a / (double)b;
 }
 void func()
 {
     SmartPtr<int> p1(new int);
     SmartPtr<int>p = new int;
     //将指针交付给对象管理,可以不需要显示调用
     cout << div() << endl;
 }
 int main()
 {
     try
     {
         func();
     }
     catch (exception& e)
     {
         cout << e.what() << endl;
     }
     return 0;
 }

智能指针的原理

上述的SmartPtr不能算是完整的指针,指针具有解引用等行为,所以还应该重载上* 和 ->等操作符。

 
template<class T>
 class SmartPtr
 {
 public:
     SmartPtr(T* ptr)
         :_ptr(ptr)
     {}
     ~SmartPtr()
     {
         if (_ptr)
             delete  _ptr;
     }
     T& operator*()
     {
         return *_ptr;
     }
     T* operator->()
     {
         return _ptr;
     }

总结 1.RAII特性 2.重载operator*() 和 operator->() 模拟指针行为

std::auto _ptr

C++98 版本中就提供了auto _ptr 的智能指针。 auto _ptr实现原理:管理权限转移的思想。原先权利全在丞相手中,现在分配到了三省之中。 //C++98 管理权限转移 auto_ptr

 
namespace bit
 {
     template<class T>
     class auto_ptr
     {
     public:
         auto_ptr(T* ptr)
             :_ptr(ptr)
         {}
         ~auto_ptr()
         {
             if (_ptr)
                 delete _ptr;
         }
         auto_ptr(auto_ptr<T>& sp)
             :_ptr(sp._ptr)
         {
             //权限转移
             sp._ptr = nullptr;
         }
         auto_ptr<T>& operator=(auto_ptr<T>& sp)
         {
             if (this != &sp)//检测是否是给自己赋值
             {
                 if (_ptr)
                     delete _ptr;//释放资源
                 _ptr = sp._ptr;
                 sp._ptr = nullptr;
             }
             return *this;
         }
         T& operator*()
         {
             return *_ptr;
         }
         T* operator->()
         {
             return _ptr;
         }
 ​
     private:
         T* _ptr;
     };
 }
 ​
 int main()
 {
     bit::auto_ptr<int> sp1(new int);
     bit::auto_ptr<int> sp2(sp1);//管理权限实现转移
     //此时sp1 = nullptr,即sp1悬空
     *sp2 = 20;
     //cout << *sp1 << endl;//会报错
     cout << *sp2 << endl;
     return 0;
 }

结论:auto_ptr是一个失败的设计,它会导致权限指针的悬空,很容易导致非法访问。

std::unique_ptr

C++11提供的新型智能指针。 unique_ptr实现原理:简单粗暴的防止拷贝

 
namespace bit
 {
     template<class T>
     class auto_ptr
     {
     public:
         unique_ptr(T* ptr)
             :_ptr(ptr)
         {}
         ~unique_ptr()
         {
             if (_ptr)
                 delete _ptr;
         }
         unique_ptr(unique_ptr<T>& sp) = delete;
         unique_ptr<T>& operator=(unique_ptr<T>& sp) = delete;
         T& operator*()
         {
             return *_ptr;
         }
         T* operator->()
         {
             return _ptr;
         }
     private:
         T* _ptr;
     };
 }

std::shared_ptr

C++11提供支持拷贝的shared_ptr shared_ptr的原理:通过引用计数的方式来实现多个sharend_ptr对象之间的资源共享。 1.shared_ptr在其内部,给每个资源都维护着一份计数,用来记录该份资源被几个对象共享。 2.在对象被销毁时(调用析构函数)对象的计数器自减。 3.如果引用计数为0,那就说明该对象是最后一个,就必须释放资源。 4.如果不是0,就不能释放资源,否则就会产生野指针。

 
namespace bit {
     template<class T>
     class shared_ptr
     {
     public:
         shared_ptr(T* ptr)
             :_ptr(ptr)
             ,_pCount(new int(1))//计数器,初始化为1
         {}
         
         void release()
         {
             if (_ptr && --(*_pCount) == 0)
             {
                 delete _ptr;
                 delete _pCount;
                 _ptr = nullptr;
                 _pCount = nullptr;
             }
         }
         ~shared_ptr()
         {
             release();
         }
         shared_ptr(const shared_ptr<T>& sp)
             :_ptr(sp._ptr)
             , _pCount(sp._pCount)
         {
             ++(*_pCount);
         }
 ​
         //sp1 = sp3;
         shared_ptr<T>& operator=(const shared_ptr<T>& sp)
         {
             //1.防止自己给自己赋值
             if (_ptr != sp._ptr)
             {
                 //释放this手中的资源,来接收sp手里的资源
                 release();
 ​
                 //交接
                 _ptr = sp._ptr;
                 _pCount = sp._pCount;
                 (*_pCount)++;//自加
             }
             return *this;
         }
         T& operator*()
         {
             return *_ptr;
         }
         T* operator->()
         {
             return _ptr;
         }
         T* get()
         {
             return _ptr;
         }
     private:
         T* _ptr;
         int* _pCount;//计数器
     };
 }
 ​
 int main()
 {
     bit::shared_ptr<int> sp1(new int);
     bit::shared_ptr<int> sp2(new int);//sp1 和 sp2 共同管理一份资源
     bit::shared_ptr<int> sp3(new int);
     sp3 = sp2;
     return 0;
 }

weak_ptr

shared_ptr已经足够完美,但是其解决不循环引用的问题。

 
struct ListNode
 {
     std::shared_ptr<ListNode> _prev = nullptr;
     std::shared_ptr<ListNode> _next = nullptr;
     int val = 0;
     ~ListNode()
     {
         cout << "~ListNode()" << endl;
     }
 };
 ​
 int main()
 {
     std::shared_ptr<ListNode> p1(new ListNode);
     std::shared_ptr<ListNode> p2(new ListNode);
 ​
     cout << p1.use_count() << endl;
     cout << p2.use_count() << endl;
     //循环引用
     p1->_next = p2;
     p2->_prev = p1;
 ​
     cout << p1.use_count() << endl;
     cout << p2.use_count() << endl;
     return 0;
 }

 

 

程序结束不曾释放空间,说明shared_ptr不能处理循环引用的问题。

 

 

使用weak_ptr就可以解决问题。 解决方式:在引用计数的情况下,把节点的_prev 和 _next 改成weak_ptr.

关于weak_ptr:

 
//不参与指向资源的节点的释放
     template<class T>
     class weak_ptr
     {
     public:
         weak_ptr()
             :_ptr(nullptr)
         {}
 ​
         weak_ptr(const shared_ptr<T>& sp)
             :_ptr(sp.get())
         {}
 ​
         weak_ptr<T>& operator=(const shared_ptr<T>& sp)
         {
             if (_ptr != sp.get())
             {
                 _ptr = sp.get();
             }
             return *this;
         }
         T& operator*()
         {
             return *_ptr;
         }
         T* operator->()
         {
             return _ptr;
         }
     private:
         T* _ptr;
     };
针对上述修改:

 struct ListNode
 {
     bit::weak_ptr<ListNode> _prev ;
     bit::weak_ptr<ListNode> _next ;
     int val = 0;
     ~ListNode()
     {
         cout << "~ListNode()" << endl;
     }
 };
 ​
 ​
 int main()
 {
     bit::shared_ptr<ListNode> p1(new ListNode);
     bit::shared_ptr<ListNode> p2(new ListNode);
 ​
     cout << p1.use_count() << endl;
     cout << p2.use_count() << endl;
     //
     p1->_next = p2;
     p2->_prev = p1;
 ​
     cout << p1.use_count() << endl;
     cout << p2.use_count() << endl;
     return 0;
 }

但是shared_ptr默认时delete 对象,如果是malloc new[]的形式,就会报错。 所以出现了配置器

 namespace bit
 {
     template<class T,class D = default_delete<T>>//仿函数
     class unique_ptr
     {
     public:
         unique_ptr(T* ptr)
             :_ptr(ptr)
         {}
         ~unique_ptr()
         {
             if (_ptr)
             {
                 D del;
                 del(_ptr);
                 //delete _ptr;
             }
         }
         unique_ptr(unique_ptr<T>& sp) = delete;
         unique_ptr<T>& operator=(unique_ptr<T>& sp) = delete;
         T& operator*()
         {
             return *_ptr;
         }
         T* operator->()
         {
             return _ptr;
         }
     private:
         T* _ptr;
     };
 }
 template<class T>
 struct DelArayy
 {
     void operator()(T* ptr)
     {
         cout << "delete[]" << endl;
         delete[] ptr;
         ptr = nullptr;
     }
 };
 ​
 template<class T>
 struct defmalloc
 {
     void operator()(T* ptr)
     {
         cout << "free" << endl;
         free(ptr);
         ptr = nullptr;
     }
 };
 ​
 template<class T>
 struct Fclose
 {
     void operator()(T* ptr)
     {
         cout << "fclose" << endl;
         fclose(ptr);
         ptr = nullptr;
     }
 };
 template<class T>
 struct default_delete
 {
     void operator()(T* ptr)
     {
         cout << "default_delete" << endl;
         delete ptr;
     }
 };
 class Date
 {
 public:
     ~Date()
     {
         cout << "~Date()" << endl;
     }
 private:
     int _year = 1;
     int _minth = 1;
     int _day = 1;
 };
 int main()
 {
     //bit::shared_ptr<Date> up1(new Date);
     //bit::unique_ptr<Date,DelArayy<Date>> up1(new Date[10]);
     //bit::unique_ptr<Date, defmalloc<Date>> up2((Date*)malloc(sizeof(Date)*10));
     //bit::unique_ptr<Date, Fclose<Date>> up3((FILE*)fopen("test.cpp","r"));
     return 0;
 }

对于对象类,就可以使用仿函数或者lambda表达式。使用库提供的就好

 

 

int main()
 {
     //std::shared_ptr<Date> up2(new Date [10] , DelArayy<Date>());
     std::shared_ptr<Date> up2(new Date[10], [](Date* ptr) {delete[] ptr; });
     return 0;
 }

C++11和boost的中智能指针的关系

1.C++98产生了第一个智能指针auto_ptr。  2.C++ boost给出了更实用的scoped_ptr 和 shared_ptr 和 weak_ptr  3.C++ TRI,引入了shared_ptr等,但是TRI不是标准版。  4.C++11,引入了unique_ptr 和 shared_ptr 和 weak_ptr .unique_ptr 对应 boost中的scoped_ptr实现。并且智能指针的实现原理是参照boost中的实现的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值