智能指针的模拟实现shared_ptr 循环引用 定置删除器

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了智能指针的模拟实现shared_ptr 循环引用 定置删除器相关的知识,希望对你有一定的参考价值。

auto_ptr与scoped_ptr的实现见本人的上篇博客。

三、shared_ptr

shared_ptr的实现原理是通过引用计数来实现,只有当引用计数为1时才释放空间,否则只需将引用计数减1.拷贝和赋值将引用计数加1,具体代码如下:

template <typename T>
class SharedPtr
{
public:
	SharedPtr();
	SharedPtr(T* ptr);
	SharedPtr(const SharedPtr<T>& ap);
	~SharedPtr();
	//SharedPtr<T>& operator=(const SharedPtr<T>& ptr);//传统写法
	SharedPtr<T>& operator=(SharedPtr<T> ap);//现代写法
	T& operator*()const;
	T* operator->()const;
	long GetCount()const;
	T* GetPtr()const;
protected:
	void _Realease();
protected:
	T* _ptr;
	long* _pCount;
};
template <typename T>
SharedPtr<T>::SharedPtr() :_ptr(NULL), _pCount(new long(1))
{}
template <typename T>
SharedPtr<T>::SharedPtr(T* ptr) : _ptr(ptr), _pCount(new long(1))
{}
template <typename T>
SharedPtr<T>::SharedPtr(const SharedPtr<T>& ap) : _ptr(ap._ptr), _pCount(ap._pCount)
{
	++(*this->_pCount);
}
template <typename T>
SharedPtr<T>::~SharedPtr()
{
	this->_Realease();
}
//template <typename T>//传统写法
//SharedPtr<T>& SharedPtr<T>::operator=(const SharedPtr<T>& ap)
//{
//	if (this->_ptr != ap._ptr)
//	{
//		this->_Realease();
//		this->_ptr = ap._ptr;
//		this->_pCount = ap._pCount;
//		++(*this->_pCount);
//	}
//	return *this;
//}
template <typename T>//现代写法
SharedPtr<T>& SharedPtr<T>::operator=(SharedPtr<T> ap)
{
	swap(this->_ptr, ap._ptr);
	swap(this->_pCount, ap._pCount);
	return *this;
}
template <typename T>
T& SharedPtr<T>::operator*()const
{
	return *(this->_ptr);
}
template <typename T>
T* SharedPtr<T>::operator->()const
{
	return this->_ptr;
}
template <typename T>
long SharedPtr<T>::GetCount()const
{
	return *(this->_pCount);
}
template <typename T>
T* SharedPtr<T>::GetPtr()const
{
	return this->_ptr;
}
template <typename T>
void SharedPtr<T>::_Realease()
{
	if (--(*this->_pCount) == 0)
	{
		delete this->_ptr;
		delete this->_pCount;
	}
}

然而上面用引用计数实现的简化版看起来不错,但却存在以下问题:

1、引用计数更新存在着线程安全

2、循环引用

3、定置删除器,比如要关闭一个文件,用malloc开辟出来的空间,上述代码均会出现问题

问题1的解决需要对改变引用计数时加锁。我们暂时不讨论,一下我们主要看第二个和第三个问题。

循环引用:

比如有以下结构体和主函数:

struct ListNode
{
	shared_ptr<ListNode> _prev; //shared_ptr为库文件中实现的,只需包memory即可使用
	shared_ptr<ListNode> _next;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> prev; //语句1
	shared_ptr<ListNode> next; //语句2
	prev->_next = next;        //语句3
	next->_prev = prev;        //语句4
}

经语句1、2之后prev的引用计数为1,经3、4后为2,但是最后两个对象均不能释放,因为prev的要释放的前提是next释放,而next的释放又依赖于prev的释放。最后就形成了循环引用,谁都是放不了。解决方案如下:

struct ListNode
{
	weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

因为weak_ptr(弱引用智能指针)会对引用计数会做特殊处理(上述情况不加1)。

定置删除器和空间分配器(ps:空间分配器的定置特殊场景下才会这样使用)

eg:如果指针是一个指向文件类型的,在析构函数中只需关闭文件即可,而不是释放空间;如果空间是通过,malloc出来的,那么在析构函数中要调用free函数,而不是delete操作符。上述问题通过仿函数就可以解决。具体代码如下:

template <typename T,class Deleter = Del<T>>
class SharedPtr
{
public:
	SharedPtr(T* ptr);
	SharedPtr(T* ptr, Deleter del);
	SharedPtr(const SharedPtr<T, Deleter>& ap);
	~SharedPtr();
	//SharedPtr<T, Deleter>& operator=(const SharedPtr<T, Deleter>& ptr);//传统写法
	//现代写法
	SharedPtr<T, Deleter>& operator=(SharedPtr<T, Deleter> ap);
	T& operator*()const;
	T* operator->()const;
	long GetCount()const;
	T* GetPtr()const;
protected:
	void _Realease();
protected:
	T* _ptr;
	long* _pCount;
	Deleter _del;
};

template <typename T, class Deleter = Del<T>>
SharedPtr<T, Deleter>::SharedPtr(T* ptr) :_ptr(ptr), _pCount(new long(1))
{}

template <typename T, class Deleter = Del<T>>
SharedPtr<T, Deleter>::SharedPtr(T* ptr, Deleter del) : _ptr(ptr), _pCount(new long(1)), _del(del)
{}

template <typename T, class Deleter = Del<T>>
SharedPtr<T, Deleter>::SharedPtr(const SharedPtr<T, Deleter>& ap) : _ptr(ap._ptr), _pCount(ap._pCount), _del(ap._del)
{
	++(*this->_pCount);
}

template <typename T, class Deleter = Del<T>>
SharedPtr<T, Deleter>::~SharedPtr()
{
	this->_Realease();
}

//template <typename T, class Deleter = Del<T>>//传统写法
//SharedPtr<T, Deleter>& SharedPtr<T, Deleter>::operator=(SharedPtr<T, Deleter> ap)
//{
//	if (this->_ptr != ap._ptr)
//	{
//		this->_Realease();
//		this->_ptr = ap._ptr;
//		this->_pCount = ap._pCount;
//		++(*this->_pCount);
//	}
//	return *this;
//}

template <typename T, class Deleter = Del<T>>//现代写法
SharedPtr<T, Deleter>& SharedPtr<T, Deleter>::operator=(SharedPtr<T, Deleter> ap)
{
	swap(this->_ptr, ap._ptr);
	swap(this->_pCount, ap._pCount);
	swap(this->_del, ap._del);
	return *this;
}

template <typename T, class Deleter = Del<T>>
T& SharedPtr<T, Deleter>::operator*()const
{
	return *(this->_ptr);
}

template <typename T, class Deleter = Del<T>>
T* SharedPtr<T, Deleter>::operator->()const
{
	return this->_ptr;
}

template <typename T, class Deleter = Del<T>>
long SharedPtr<T, Deleter>::GetCount()const
{
	return *(this->_pCount);
}

template <typename T, class Deleter = Del<T>>
T* SharedPtr<T, Deleter>::GetPtr()const
{
	return this->_ptr;
}

template <typename T, class Deleter = Del<T>>
void SharedPtr<T, Deleter>::_Realease()
{
	if (--(*this->_pCount) == 0)
	{
		_del(_ptr);
		delete this->_pCount;
	}
}

以上是关于智能指针的模拟实现shared_ptr 循环引用 定置删除器的主要内容,如果未能解决你的问题,请参考以下文章

C++进阶---智能指针

C++智能指针shared_ptr 定位删除器(仿函数)

智能指针循环引用--转

shared_ptr智能指针为什么循环引用会出问题

深入学习c++--智能指针 weak_ptr(打破shared_ptr循环引用)

智能指针的循环引用与解决