前面介绍的auto_ptr和unique_ptr都存在着些许的缺陷,显得不是那么的“智能”,下面我们来看一下较为智能的shared_ptr的设计思路(一块空间、计数器、锁):
与前两者不同的是shared_ptr用一个count的引用计数将指向同一份内存空间的指针,用_count来表示,这样在析构的时候,会判断_count是否为0,再决定是否示释放空间:
但是并不仅仅是一个_count就能解决的问题,在遇到多线程编程的时候,_count就变成了共享资源,而对_count的操作也就需要多留心一下,为了保证对_count的操作是安全可靠的,所以就引入了mutex
当然shared_ptr也并不是最完美的,还存在着一点的小瑕疵,点击查看解决方案
#include <thread> #include <mutex> using std::mutex; using std::cout; using std::endl; template <typename T> class Shared_ptr { public: Shared_ptr(T* ptr = nullptr) :_ptr(ptr) ,_pCount(new int(1)) , _pMutex(new mutex) { if (ptr == nullptr) *_pCount = 0; } ~Shared_ptr() { Realase(); } Shared_ptr(const Shared_ptr<T>& p) :_ptr(p._ptr) , _pCount(p._pCount) , _pMutex(p._pMutex) { if (_ptr != nullptr) AddCount(); } Shared_ptr& operator=(const Shared_ptr<T>& p) { if (_ptr != p._ptr) { Realase();//要释放一下之前所管理的空间,否则会影响到p之前所管理的空间不能正常释放 _ptr = p._ptr; _pCount = p._pCount; _pMutex = p._pMutex; if (_ptr != nullptr) AddCount(); } return *this; } T& operator*() { return *this; } T* operator->() { return this; } int AddCount() { _pMutex->lock(); ++(*_pCount); _pMutex->unlock(); return *_pCount; } int SubCount() { _pMutex->lock(); --(*_pCount); _pMutex->unlock(); return *_pCount; } int GetCount() { return *_pCount; } private: void Realase() { if (_ptr && SubCount() == 0) { delete _ptr; delete _pCount; } } private: T* _ptr; int* _pCount; std::mutex* _pMutex; };以下是我的测试用例:
下图是第一个断点之后的内容:
下图是第二个断点之后的内容
之前提过的shared_ptr会有一个循环引用的缺点,会造成不能正常析构的问题:
#include <memory> class BB; class AA { public: AA() { cout << "AA::AA() called" << endl; } ~AA() { cout << "AA::~AA() called" << endl; } shared_ptr<BB> m_bb_ptr; //因为BB还未定义,所以在之前先声明一下 }; class BB { public: BB() { cout << "BB::BB() called" << endl; } ~BB() { cout << "BB::~BB() called" << endl; } shared_ptr<AA> m_aa_ptr; //! }; int main() { shared_ptr<AA> ptr_a(new AA); shared_ptr<BB> ptr_b(new BB); cout << "ptr_a use_count: " << ptr_a.use_count() << endl; cout << "ptr_b use_count: " << ptr_b.use_count() << endl; //下面两句导致了AA与BB的循环引用,结果就是AA和BB对象都不会析构 ptr_a->m_bb_ptr = ptr_b; ptr_b->m_aa_ptr = ptr_a; cout << "ptr_a use_count: " << ptr_a.use_count() << endl; cout << "ptr_b use_count: " << ptr_b.use_count() << endl; }在ptr_a和ptr_b对象中的shared_ptr的没有互相引用之前:ptr_a中有m_bb_ptr,而m_bb_ptr中又有m_aa_ptr就截止了。
在相互连接起来之后:就会无限的循环引用下去,这样两个shared_ptr指针相互保管,谁都不回先释放。所以不能执行析构函数,实际上两个对象ptr_a和ptr_b所指向的资源都只有一个指针指向,而两个对象中的成员都指向了另一个对象。所以造成了歧义,这样不会调用析构函数,造成内存泄漏。
将两个类中的成员类型定义为weak_ptr就可以解决这个问题,因为weak_ptr , 它的构造和析构不会引起引用记数的增加或减少
这是因为weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数【0】。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使weak_ptr指向对象,对象也会被释放。
将AA类和BB类中的成员定义为weak_ptr我们就可以达到正常的逻辑了: 能够正常的析构