前面介绍的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我们就可以达到正常的逻辑了: 能够正常的析构
本文介绍了shared_ptr的设计原理,包括引用计数、互斥锁的应用及其在多线程环境下的安全性保障,并通过实例演示了如何避免循环引用导致的内存泄漏。

863

被折叠的 条评论
为什么被折叠?



