最早的智能指针在Boost库里面,Boost库是为C++标准库提供扩展的一些C++程序的总称,由Boost社区组织开发维护。
“智能”指针看上去是指针,实际上是附加了语义的对象。shared_ptr与scoped_ptr一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)它时才删除被包装的动态分配的对象。shared_ptr也可以安全地放到标准容器中,并弥补了auto_ptr因为转移语义而不能把指针作为STL容器元素的缺陷。任何情况下都不要使用auto_ptr。 一、模拟实现shared_ptr的基本功能: #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; template<class T> class SharpedPtr { public: SharpedPtr(T* ptr = 0)//构造函数 :_ptr(ptr) , _pCount(NULL) { if (ptr) { _pCount = new int(1); } } SharpedPtr(const SharpedPtr<T>& ap)//拷贝构造 :_ptr(ap._ptr) , _pCount(ap._pCount) { if (_ptr) { ++UseCount(); } } //ap1 = ap2;赋值运算符重载 //①ap1内部封装的指针为空 //②ap1独占一块空间 //③ap1与其他对象共享一块空间 SharpedPtr& operator=(const SharpedPtr<int>& ap) { if (this != &ap) { if (_ptr) { Release(); } //ap1与其他对象共享一块空间 _ptr = ap._ptr; _pCount = ap._pCount; ++UseCount(); } return *this; } //析构函数 ~SharpedPtr() { Release(); } //检查引用计数并释放空间 void Release() { if (0 == --UseCount()) { delete _pCount; _pCount = NULL; delete _ptr; _ptr = NULL; } } //获取引用计数 int& UseCount() { return *_pCount; } //为了使其更像一个指针,所完成的基本操作 T* operator->() { return _ptr; } T& operator*() { return *_ptr; } private: T* _ptr; int* _pCount; }; void TestSharpedPtr() { SharpedPtr<int> ap1(new int(10)); SharpedPtr<int> ap2(new int(20)); SharpedPtr<int> ap3(ap1); SharpedPtr<int> ap4; ap4 = ap1; *ap1 = 1; *ap2 = 2; *ap3 = 3; *ap4 = 4; } int main() { TestSharpedPtr(); return 0; } 虽然我们上面的程序看起来还可以,但实际上还存在以下问题: 但是还存在以下问题: 线程安全问题:
shared_ptr 本身不是 100% 线程安全的。它的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。shared_ptr 的线程安全级别和内建类型、标准库容器、string 一样,即:
一个 shared_ptr 实体可被多个线程同时读取;两个的 shared_ptr 实体可以被两个线程同时写入,“析构”算写操作;如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。 2.对于管理的特定类型的指针的释放不合理造成的程序崩溃: ① 我们都会有一种普遍的认知,shared_ptr智能指针只能用来管理动态开辟的空间,实际不然,shared_ptr是用来管理资源的,这里的资源并不仅仅是动态开辟的空间,还有例如:打开文件,其维护一个文件指针,那么在程序快要结束时,需要关闭文件; 还有就是使用malloc来动态开辟的空间,需要使用free来释放,否则,不管管理什么资源,都使用delete来释放空间,有时就会使得程序崩溃。 所以呢,我们就需要为其定制删除器 。 #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; //为解决文件指针 struct Fclose { void operator()(FILE*& fp) { cout<< "Fclose()" << endl; fclose(fp); fp = NULL; } }; //为解决malloc开辟的空间 template<class T> struct Free { void operator()(T*& p) { free(p); p = NULL; } }; //一般情况下(使用new动态开辟的空间) template<class T> class Delete { public : void operator()(T*& p) { delete p; p = NULL; } }; template<class T, class Destory = Delete<T> > class SharedPtr { public : SharedPtr(T* ptr = 0)//构造函数 :_ptr(ptr) ,_pCount(NULL) { if (_ptr) { _pCount = new int(1); } } SharedPtr(const SharedPtr<T>& sp)//拷贝构造函数 :_ptr(sp._ptr) , _pCount(sp._pCount) { if (_ptr) { ++GetRef(); } } //sp1 = sp2 SharedPtr<T>& operator=(const SharedPtr<T>& sp) { //可有三种情况:①sp1._ptr = NULL ②sp1的引用计数为1 ③sp1的引用计数大于1 if (this != &sp) { Release(); _ptr = sp._ptr; _pCount = sp._pCount; ++GetRef(); } return *this; } //辅助函数 void Release() { if (_ptr && --GetRef() == 0) { Destory()(_ptr); delete _pCount; _pCount = NULL; } } //析构函数 ~SharedPtr() { Release(); } int& GetRef() { return *_pCount; } private: T* _ptr; int* _pCount; }; void Test2() { FILE* sp1 = fopen("test.txt", "rb"); SharedPtr<FILE, Fclose> sp2(sp1); int* sp3 = (int*)malloc(sizeof(int)); SharedPtr<int, Free<int> >sp4(sp3); int* sp5 = new int(1); SharedPtr<int> sp6(sp5); } int main() { Test2(); return 0; } 3.循环引用问题; 先来看下面一个示例: 下面的代码使用的都是c++标准库里的智能指针,源码也是。boost库的智能指针和c++标准库中的智能指针在底层实现机制是一样的。 #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; #include"boost/shared_ptr.hpp" struct Node { Node(int value) :_value(value) { cout << "Node()" << endl; } ~Node() { cout << "~Node()" << endl; } shared_ptr<Node> _prev; shared_ptr<Node> _next; int _value; }; void Test2() { shared_ptr<Node> sp1(new Node(1)); shared_ptr<Node> sp2(new Node(2)); cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; sp1->_next = sp2; sp2->_prev = sp1; cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; } int main() { Test2(); return 0; } 可以看出,在这里我们使用了双向链表来说明这个问题,并且双向链表中的指针都是使用shared_ptr来维护的,在这里我使用的是标准库里的 shared_ptr智能指针。 从下图可以看出,构造的sp1和sp2在出它们的作用域时(即Test2())都没有被析构,从而造成了内存泄漏。由于先构造的后释放,后构造的先释放可知,先释放的是sp2,那么因为它的引用计数为2,减去1之后就成为了1,不能释放空间,因为还有其他的对象在管理这块空间。但是sp2这个变量已经被销毁,因为它是栈上的变量,但是sp2管理的堆上的空间并没有释放。 接下来释放sp1,同样,先检查引用计数,由于sp1的引用计数也是2,所以减1后成为1,也不会释放sp1管理的动态空间。 通俗点讲:就是sp2要释放,那么必须等p1释放了,而sp1要释放,必须等sp2释放,所以,最终,它们两个都没有释放空间。 所以,造成了内存泄漏。 下面让我们一起来剖析源码是怎样实现这种情况的?
上图只给出了简单的过程,下面贴出调用过程完整的源码:
shared_ptr() _NOEXCEPT { // construct empty shared_ptr } _Ptr_base() : _Ptr(0), _Rep(0) { // construct } 上面的构造函数连续调用了两次,构造节点内的_prev和_next。 template<class _Ux> explicit shared_ptr(_Ux *_Px) { // construct shared_ptr object that owns _Px _Resetp(_Px); } _Ptr_base() : _Ptr(0), _Rep(0) { // construct } template<class _Ux> void _Resetp(_Ux *_Px) { // release, take ownership of _Px _TRY_BEGIN // allocate control block and reset _Resetp0(_Px, new _Ref_count<_Ux>(_Px)); _CATCH_ALL // allocation failed, delete resource delete _Px; _RERAISE; _CATCH_END } _Ref_count(_Ty *_Px) : _Ref_count_base(), _Ptr(_Px) { // construct } _Ref_count_base() { // construct _Init_atomic_counter(_Uses, 1); _Init_atomic_counter(_Weaks, 1); } template<class _Ux> void _Resetp0(_Ux *_Px, _Ref_count_base *_Rx) { // release resource and take ownership of _Px this->_Reset0(_Px, _Rx); _Enable_shared(_Px, _Rx); } }; void _Reset0(_Ty *_Other_ptr, _Ref_count_base *_Other_rep) { // release resource and take new resource if (_Rep != 0) _Rep->_Decref();//没有执行 _Rep = _Other_rep; _Ptr = _Other_ptr; } inline void _Enable_shared(const volatile void *, const volatile void *) { // not derived from enable_shared_from_this; do nothing } 所以,构造智能指针sp1主要完成两件事情: 1、给引用计数开辟空间,并初始化uses和weaks均为1 2、将节点的空间交给_Rep来管理,将引用计数的空间交给_Ptr来管理。 sp2和sp1的构造过程相同。同样:下面贴出上述过程的源码: _Ty *operator->() const _NOEXCEPT { // return pointer to resource return (this->_Get()); } _Myt& operator=(const _Myt& _Right) _NOEXCEPT { // assign shared ownership of resource owned by _Right shared_ptr(_Right).swap(*this); return (*this); } shared_ptr(const _Myt& _Other) _NOEXCEPT { // construct shared_ptr object that owns same resource as _Other this->_Reset(_Other); } _Ptr_base() : _Ptr(0), _Rep(0) { // construct } template<class _Ty2> void _Reset(const _Ptr_base<_Ty2>& _Other) { // release resource and take ownership of _Other._Ptr _Reset(_Other._Ptr, _Other._Rep); } void _Reset(_Ty *_Other_ptr, _Ref_count_base *_Other_rep) { // release resource and take _Other_ptr through _Other_rep if (_Other_rep) _Other_rep->_Incref(); _Reset0(_Other_ptr, _Other_rep); } void _Incref() { // increment use count _MT_INCR(_Mtx, _Uses); } void _Reset0(_Ty *_Other_ptr, _Ref_count_base *_Other_rep) { // release resource and take new resource if (_Rep != 0) _Rep->_Decref(); _Rep = _Other_rep; _Ptr = _Other_ptr; } 上述调用过程,主要完成: 1.将sp2管理的引用计数中的uses加至2 2.sp1中的_next节点中的_Rep管理sp2的引用计数,_next节点中的_Ptr管理sp2节点 sp2->_prev = sp1; 其实调用过程同sp1->_next = sp2调用过程,主要完成: 1.将sp1管理的引用计数中的uses加至2 2.sp2中的_prev节点中的_Rep管理sp1的引用计数,_prev节点中的_Ptr管理sp1节点. 总体结构图如下:
图1
下面我们一起来看看析构的过程: 先析构sp2,下面是析构sp2的过程,可以看出没有释放空间(节点空间和引用计数空间),sp2析构完成之后,再去析构sp1,sp1的析构过程和sp2的析构过程相同,所以由此可知,一共有四块动态开辟的空间,但是都没有释放。 这里只贴出sp2的析构过程。 ~shared_ptr() _NOEXCEPT { // release resource this->_Decref(); } void _Decref() { // decrement reference count if (_Rep != 0) _Rep->_Decref(); } void _Decref() { // decrement use count if (_MT_DECR(_Mtx, _Uses) == 0)//uses为2,经过这条语句之后减为1 { // destroy managed resource, decrement weak reference count _Destroy();//不执行 _Decwref();//不执行 } } 由此引入weak_ptr,可以这么说,weak_ptr是为了shared_ptr而生,weak_ptr就是为解决shared_ptr的循环引用问题而产生的。 它必须依赖于shared_ptr,不能直接管理一块动态开辟的空间。 ------------------------------------------------------------------------- 下面让我们一起来看weak_ptr是如何来解决循环引用的。 因为下面的过程和上面的过程大多数相同,所以在这里就不详细讲解了。 #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; #include"boost/shared_ptr.hpp" struct Node { Node(int value) :_value(value) { cout << "Node()" << endl; } ~Node() { cout << "~Node()" << endl; } weak_ptr<Node> _prev; weak_ptr<Node> _next; int _value; }; void Test2() { shared_ptr<Node> sp1(new Node(1)); shared_ptr<Node> sp2(new Node(2)); cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; sp1->_next = sp2; sp2->_prev = sp1; cout << sp1.use_count() << endl; cout << sp2.use_count() << endl; } int main() { Test2(); return 0; }
使用源码分析上述过程: 先来看构造sp1和sp2的过程: shared_ptr<Node> sp1(new Node(1)); shared_ptr<Node> sp2(new Node(2)); Node(int value) :_value(value) { cout << "Node()" << endl; } //与上面的区别在这里(Node里的_prev和_next分别为weak_ptr智能指针) weak_ptr() _NOEXCEPT { // construct empty weak_ptr object } _Ptr_base() : _Ptr(0), _Rep(0) { // construct } 上面过程同样连续执行两次(构造_prev和_next) 下面过程也是执行两次(构造sp1和sp2),和上面的过程相同 template<class _Ux> explicit shared_ptr(_Ux *_Px) { // construct shared_ptr object that owns _Px _Resetp(_Px); } _Ptr_base() : _Ptr(0), _Rep(0) { // construct } template<class _Ux> void _Resetp(_Ux *_Px) { // release, take ownership of _Px _TRY_BEGIN // allocate control block and reset _Resetp0(_Px, new _Ref_count<_Ux>(_Px)); _CATCH_ALL // allocation failed, delete resource delete _Px; _RERAISE; _CATCH_END } _Ref_count(_Ty *_Px) : _Ref_count_base(), _Ptr(_Px) { // construct } _Ref_count_base() { // construct _Init_atomic_counter(_Uses, 1); _Init_atomic_counter(_Weaks, 1); } template<class _Ux> void _Resetp0(_Ux *_Px, _Ref_count_base *_Rx) { // release resource and take ownership of _Px this->_Reset0(_Px, _Rx); _Enable_shared(_Px, _Rx); } }; void _Reset0(_Ty *_Other_ptr, _Ref_count_base *_Other_rep) { // release resource and take new resource if (_Rep != 0) _Rep->_Decref(); _Rep = _Other_rep; _Ptr = _Other_ptr; } inline void _Enable_shared(const volatile void *, const volatile void *) { // not derived from enable_shared_from_this; do nothing } sp1->_next = sp2; sp2->_prev = sp1; 源码分析上面两个语句的执行过程: sp1->_next = sp2; _Ty *operator->() const _NOEXCEPT { // return pointer to resource return (this->_Get()); } template<class _Ty2> weak_ptr& operator=(const shared_ptr<_Ty2>& _Right) _NOEXCEPT//_Right就是sp2 { // assign from _Right this->_Resetw(_Right); return (*this); } template<class _Ty2> void _Resetw(const _Ptr_base<_Ty2>& _Other)//_Other为sp2 { // release weak reference to resource and take _Other._Ptr _Resetw(_Other._Ptr, _Other._Rep); } template<class _Ty2> void _Resetw(_Ty2 *_Other_ptr, _Ref_count_base *_Other_rep) { // point to _Other_ptr through _Other_rep if (_Other_rep)//增加sp2的弱引用计数,sp2.weaks加1 = 2 _Other_rep->_Incwref(); if (_Rep != 0)//sp1->next._Rep为0 _Rep->_Decwref();//不执行 _Rep = _Other_rep;//sp1->_next._Rep = sp2._Rep _Ptr = _Other_ptr;//sp1->_next._Ptr = sp2._Ptr } void _Incwref() { // increment weak reference count _MT_INCR(_Mtx, _Weaks);//将weaks从1修改为2 } sp2->_prev = sp1; _Ty *operator->() const _NOEXCEPT { // return pointer to resource return (this->_Get()); } template<class _Ty2>//_Right就是sp1 weak_ptr& operator=(const shared_ptr<_Ty2>& _Right) _NOEXCEPT { // assign from _Right this->_Resetw(_Right); return (*this); } template<class _Ty2>//_Other为sp1 void _Resetw(const _Ptr_base<_Ty2>& _Other) { // release weak reference to resource and take _Other._Ptr _Resetw(_Other._Ptr, _Other._Rep); } template<class _Ty2> void _Resetw(_Ty2 *_Other_ptr, _Ref_count_base *_Other_rep) { // point to _Other_ptr through _Other_rep if (_Other_rep)//增加sp1的弱引用计数,sp1.weaks加1 = 2 _Other_rep->_Incwref(); if (_Rep != 0)//sp2->prev._Rep为0 _Rep->_Decwref();//不执行 _Rep = _Other_rep;//sp2->_prev._Rep = sp1._Rep _Ptr = _Other_ptr;//sp2->_prev._Ptr = sp1._Ptr } void _Incwref() { // increment weak reference count _MT_INCR(_Mtx, _Weaks); } 图2 分析图2与图1的区别: 1)sp1和sp2节点内的_prev和_next均为弱引用计数(_weak_ptr) 2)sp1和sp2的引用计数中的_weaks为2,_uses为1,和上面相反。 源码分析析构过程: //析构sp2 ~shared_ptr() { // release resource this->_Decref(); } void _Decref() { // decrement reference count if (_Rep != 0)//sp2._Rep不为0 _Rep->_Decref(); } void _Decref() { // decrement use count if (_MT_DECR(_Mtx, _Uses) == 0)//sp2.uses从1减为0 { // destroy managed resource, decrement weak reference count _Destroy(); _Decwref(); } } //释放节点空间(清理节点内资源(包括sp2->next和sp2->prev)) virtual void _Destroy() { // destroy managed resource delete _Ptr; } ------------------------------------------------------------------------------------- 下面都是清理sp2节点内的资源 ~Node() { cout << "~Node()" << endl; } ~weak_ptr()//清理sp2._next(弱智能指针) { // release resource this->_Decwref(); } void _Decwref() { // decrement weak reference count if (_Rep != 0)//sp2->next._Rep为0 _Rep->_Decwref();//不执行 } ~weak_ptr()//清理sp2._prev { // release resource this->_Decwref(); } void _Decwref()/sp2->prev._Rep不为0(即就是sp1的引用计数空间存在) { // decrement weak reference count if (_Rep != 0)/ _Rep->_Decwref(); } void _Decwref()//sp2->prev._Weaks从2减为1(即就是将sp1._Weaks减为1) { // decrement weak reference count if (_MT_DECR(_Mtx, _Weaks) == 0) _Delete_this();//不执行 } 调用operator delete释放节点sp2的空间 void _Decwref()//释放sp2引用计数空间 { // decrement weak reference count if (_MT_DECR(_Mtx, _Weaks) == 0)//sp2._weaks从2减为1 _Delete_this();//不执行(也就是没有释放sp2的引用计数) } ---------------------------------------------------------------------------- 析构sp1 ~shared_ptr() { // release resource this->_Decref(); } void _Decref()//减sp1的引用计数 { // decrement reference count if (_Rep != 0)//sp1._Rep不为0(sp1的引用计数不为空) _Rep->_Decref();m } void _Decref() { // decrement use count if (_MT_DECR(_Mtx, _Uses) == 0)//sp1._uses从1减为0 { // destroy managed resource, decrement weak reference count _Destroy();//释放节点空间 _Decwref();//释放sp1的引用计数 } } ------------------------------------------------------------------------------- 清理sp1节点内资源 virtual void _Destroy() { // destroy managed resource delete _Ptr;//释放sp1节点的空间(需要去清理节点内的资源(包括sp1->_next和sp1->_prev)) } ~weak_ptr()//清理sp1->next { // release resource this->_Decwref(); } void _Decwref() { // decrement weak reference count if (_MT_DECR(_Mtx, _Weaks) == 0)//sp1->next._weaks从1减为0(即就是sp2._weaks减为0) _Delete_this();//执行 } virtual void _Delete_this()//释放了sp1->_next._Rep空间(即就是sp2的引用计数空间) { // destroy self delete this; } ~weak_ptr()//清理sp1._prev { // release resource this->_Decwref(); } void _Decwref() { // decrement weak reference count if (_Rep != 0)//sp1->_prev._Rep为空 _Rep->_Decwref();//不执行 } 调用operator delete释放节点sp1空间 ----------------------------------------------------------------------------------------------------------- 释放sp1引用计数空间 void _Decwref() { // decrement weak reference count if (_MT_DECR(_Mtx, _Weaks) == 0)//sp1._weaks从1减为0 _Delete_this(); } virtual void _Delete_this()//释放sp1引用计数的空间 { // destroy self delete this; } 分析不同之处: 1)执行下面两条语句 : sp1->_next = sp2; sp2->_prev = sp1; 都是将对方的_weaks加至2 。 2)释放节点空间(关注uses的大小,若可以减为0,则释放节点空间) 释放引用计数空间(关注_weaks的大小,若可以减为0,则释放引用计数的空间) 从而解决了循环引用的问题。 下面用图简单说明下析构过程: sp2的析构过程: sp1的析构过程: 对于c++标准库里shared_ptr和weak_ptr的继承关系简单图示: 总结: 在以下情况时使用 shared_ptr :
当有多个使用者使用同一个对象,而没有一个明显的拥有者时
当要把指针存入标准库容器时
当要传送对象到库或从库获取对象,而没有明确的所有权时
当管理一些需要特殊清除方式的资源时
通过定制删除器的帮助。
由于看的源码太少,所以文中对于源码的理解不到位的地方还请各位大神指正!