智能指针——实现shared_ptr

本文介绍了shared_ptr的设计原理,包括引用计数、互斥锁的应用及其在多线程环境下的安全性保障,并通过实例演示了如何避免循环引用导致的内存泄漏。

前面介绍的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我们就可以达到正常的逻辑了: 能够正常的析构

                                                 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值