从源码理解智能指针—— shared_ptrweak_ptr

Posted 贺二公子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从源码理解智能指针—— shared_ptrweak_ptr相关的知识,希望对你有一定的参考价值。

原文地址:https://blog.csdn.net/qq_28114615/article/details/101166744


文章目录


在前面一文中,分析了auto_ptr和unique_ptr两种智能指针,二者都是独占式指针,即资源只能由一个智能指针拥有并管理。本文就来分析一下另外两种非独占式智能指针——共享式指针shared_ptrweak_ptr

共享式指针允许多个智能指针持有同一资源,并且当没有智能指针持有该资源时就自动销毁资源。为了实现共享+智能,就需要用到一个计数器,来记录某个资源被多少对象持有,当计数值为0时,就销毁该资源。

在shared_ptr和weak_ptr中,这样的计数器实际上是一个单独的类。它主要负责资源引用计数(增加、减少)以及资源的释放。此外,通过计数器,还能像unique_ptr那样指定删除器,还增加了一个空间分配器。下面就先来分析一下计数器:

计数器

计数器的根本,是一个抽象类_Ref_count_base,其定义如下:

class _Ref_count_base       //管理计数变量        虚基类
		// common code for reference counting
private:
	virtual void _Destroy() = 0;
	virtual void _Delete_this() = 0;
 
private:
	_Atomic_counter_t _Uses;    //引用计数
	_Atomic_counter_t _Weaks;   //弱引用计数
 
protected:
	_Ref_count_base()   //初始化两个计数变量为1
			// construct
		_Init_atomic_counter(_Uses, 1);
		_Init_atomic_counter(_Weaks, 1);
		
 
public:
	virtual ~_Ref_count_base() _NOEXCEPT
			// ensure that derived classes can be destroyed properly
		
 
	......
 
	unsigned int _Get_uses() const  //返回引用计数
			// return use count
		return (_Get_atomic_count(_Uses));
		
 
	void _Incref()
			// increment use count
		_MT_INCR(_Mtx, _Uses);// _Uses+1
		
 
	void _Incwref()
			// increment weak reference count
		_MT_INCR(_Mtx, _Weaks);//_Weaks+1
		
 
	void _Decref()
			// decrement use count
		if (_MT_DECR(_Mtx, _Uses) == 0)
				// destroy managed resource, decrement weak reference count
			_Destroy();
			_Decwref();  //如果资源已经被释放了,那么weak_ptr也没有任何作用了,
			
		
 
	void _Decwref()
			// decrement weak reference count
		if (_MT_DECR(_Mtx, _Weaks) == 0) //如果_Weaks-1后为0,就调用_Delete_this
			_Delete_this();  //释放当前对象
		
 
	long _Use_count() const  //返回引用计数
			// return use count
		return (_Get_uses());
		
 
	bool _Expired() const  //检测是否失效,失效是指引用计数为0
			// return true if _Uses == 0
		return (_Get_uses() == 0);
		
 
	virtual void *_Get_deleter(const _XSTD2 type_info&) const
			// return address of deleter object
		return (0);
		
	;

_Ref_count_base中主要包含以下信息:

  1. 两个纯虚函数_Destroy()和_Delete_this(),意味着二者必须在子类中实现,前者用来释放计数器对应的资源,后者用来释放计数器自身
  2. 两个计数变量_Uses和_Weaks,前者是强引用计数,主要用于shared_ptr中,后者用于weak_ptr中;
  3. 定义了对_Uses和_Weaks的增加、减少函数。如果某一次减少使得_Uses为0,那么就会调用子类中实现的_Destroy函数并且减少_Weaks;如果某一次减少使得_Weaks为0,就会调用子类中实现的_Delete_this函数;
  4. _Ref_count_base的构造会初始化_Uses和_Weaks为1;
  5. _Uses和_Weaks的增加、减少都是原子操作

显然,作为一个抽象类,不可能仅仅通过_Ref_count_base来实现计数器的,真正的计数器是子类来实现,_Ref_count_base的子类有三种:_Ref_count、_Ref_count_del和_Ref_count_del_alloc。从子类名也可以看出来,从前往后三种子类的功能更强大,体现在计数、删除器和分配器三个方面。

_Ref_count

_Ref_count就是一种计数器,它内部的成员变量只含一个资源指针_Ptr,其定义如下:

template<class _Ty>
	class _Ref_count     
	: public _Ref_count_base  //父类中管理计数变量,_Ref_count中管理其相应的资源
		// handle reference counting for object without deleter
public:
	_Ref_count(_Ty *_Px)
		: _Ref_count_base(), _Ptr(_Px)
			// construct
		
 
private:
	virtual void _Destroy()  //重写虚基类中的_Destroy
			// destroy managed resource
		delete _Ptr;   //释放资源
		
 
	virtual void _Delete_this()
			// destroy self
		delete this;  //销毁当前对象
		
 
	_Ty * _Ptr;   //资源指针
	;

该类中主要包含以下信息:

  1. _Ref_count中含有一个资源指针_Ptr,指向该计数器对应的资源;
  2. _Ref_count从_Ref_count_base_继承来的计数变量相关操作,都是针对_Ptr的;
  3. _Ref_count类中必须重写_Destroy和_Delete_this函数,前者对_Ptr进行释放,后者用来析构当前计数器对象。由于_Ref_count_base_的析构函数是虚函数,因此_Ref_count可以完全析构;
  4. _Ref_count类只接受用资源指针构造

_Ref_count_del

_Ref_count_del是第二种计数器,它除了拥有资源指针外,还拥有一个删除器实例,如下所示:

template<class _Ty,
	class _Dx>
	class _Ref_count_del
	: public _Ref_count_base //父类中管理计数变量,_Ref_count_del中管理相应的资源指针和删除器
		// handle reference counting for object with deleter
public:
	_Ref_count_del(_Ty *_Px, _Dx _Dt)
		: _Ref_count_base(), _Ptr(_Px), _Dtor(_Dt)
			// construct
		
 
	virtual void *_Get_deleter(const _XSTD2 type_info& _Typeid) const
			// return address of deleter object
		return ((void *)(_Typeid == typeid(_Dx) ? &_Dtor : 0));
		
 
private:
	virtual void _Destroy()
			// destroy managed resource
		_Dtor(_Ptr);
		
 
	virtual void _Delete_this()
			// destroy self
		delete this;
		
	//资源指针以及删除器
	_Ty * _Ptr;
	_Dx _Dtor;	// the stored destructor for the controlled object
	;

该类主要包含以下信息:

  1. _Ref_count_del除了有一个资源指针_Ptr,还有一个_Dx类型的删除器实例_Dtor;
  2. _Ref_count_del从_Ref_count_base_继承来的计数变量相关操作,都是针对_Ptr的;
  3. 重写的_Destroy函数中,并不像_Ref_count那样直接调用delete删除,而是使用的_Dtor(_Ptr),这说明删除器必须是一个仿函数类,_Delete_this和上述相同;
  4. _Ref_count_del只接受用资源指针和删除器实例来构造。

_Ref_count_del_alloc

最后一种计数器,除了包含一个删除器实例,还包含一个分配器实例,如下所示:

template<class _Ty,
	class _Dx,
	class _Alloc>
	class _Ref_count_del_alloc
	: public _Ref_count_base //父类中管理计数变量,_Ref_count_del_alloc中管理相应的资源指针、删除器和分配器
		// handle reference counting for object with deleter and allocator
public:
	typedef _Ref_count_del_alloc<_Ty, _Dx, _Alloc> _Myty;
	typedef typename _Alloc::template rebind<_Myty>::other _Myalty;
 
	_Ref_count_del_alloc(_Ty *_Px, _Dx _Dt, _Myalty _Al)
		: _Ref_count_base(), _Ptr(_Px), _Dtor(_Dt), _Myal(_Al)
			// construct
		
 
	virtual void *_Get_deleter(const _XSTD2 type_info& _Typeid) const
			// return address of deleter object
		return ((void *)(_Typeid == typeid(_Dx) ? &_Dtor : 0));//如果传入的类型与删除器类型相同,就返回删除器地址
		
 
private:
	virtual void _Destroy()
			// destroy managed resource
		_Dtor(_Ptr);   //将资源指针作为参数调用删除器
		
 
	virtual void _Delete_this()
			// destroy self
		_Myalty _Al = _Myal; 
		_Al.destroy(this);
		_Al.deallocate(this, 1);
		
	//资源指针、删除器、分配器
	_Ty * _Ptr;
	_Dx _Dtor;	// the stored destructor for the controlled object
	_Myalty _Myal;	// the stored allocator for this
	

该类主要包含以下信息:

  1. _Ref_count_del_alloc增加了一个分配器实例;
  2. _Ref_count_del_alloc从_Ref_count_base_继承来的计数变量相关操作,都是针对_Ptr的;
  3. 重写的_Destroy函数中,用仿函数类实例_Dtor来销毁资源指针,而在重写的_Delete_this中,则是通过分配器实例来进行的,可见,分配器中必须实现destroy函数和deallocate函数(这就很像STL中的allocator);
  4. _Ref_count_del_alloc只接受用资源指针、删除器实例和分配器实例来构造。

可以看到,计数器实际上是通过_Ref_count_base的三个子类来实现的,这三种子类中都包含了一个资源指针,调用的destroy函数也是用来删除资源指针所指向的“资源”,可以理解为,计数器实际上就已经绑定到了某一处资源上。从这一点上,我们也大致能猜出,shared_ptr和weak_ptr之所以能和计数器对应起来,就是靠同一处资源。

_Ptr_base

在分析shared_ptr和weak_ptr之前,需要先分析另一个很重要的类——_Ptr_base。shared_ptr和weak_ptr都继承自它,现在来看看这个类的定义。

_Ptr_base的成员变量

template<class _Ty>
	class _Ptr_base   //包含一个资源指针以及一个计数器
		// base class for shared_ptr and weak_ptr
            ......
        private:
	        _Ty *_Ptr;   //资源指针
	        _Ref_count_base *_Rep;   //计数器指针
	;

可以看到,_Ptr_base中,只有两个成员变量,_Ptr就是指向“资源”的资源指针,_Rep则是指向一个计数器实例的计数器指针了。这就相当于每个shared_ptr或者weak_ptr都通过_Rep指针指向一个计数器,而这个计数器中的资源指针也理应和这里的资源指针_Ptr相同。

构造函数

_Ptr_base只提供了一个无参构造和两种移动构造,如下所示:

_Ptr_base()//无参构造
		: _Ptr(0), _Rep(0)
			// construct
		
 
	_Ptr_base(_Myt&& _Right)  //移动构造_Ptr_base,交换两个_Ptr_base的_Ptr和_Rep,交换后_Right的_Ptr和_Rep都为0
		: _Ptr(0), _Rep(0)
			// construct _Ptr_base object that takes resource from _Right
		_Assign_rv(_STD forward<_Myt>(_Right));
		
 
	template<class _Ty2>
		_Ptr_base(_Ptr_base<_Ty2>&& _Right)//不同类型的右值构造,右值构造后_Right的_Ptr和_Rep都变为0
		: _Ptr(_Right._Ptr), _Rep(_Right._Rep)  //用_Right的两个指针来初始化当前_Ptr_base的两个指针
			// construct _Ptr_base object that takes resource from _Right
		_Right._Ptr = 0;
		_Right._Rep = 0;
		

对于无参构造,_Ptr和_Rep都会初始化为0,相当于nullptr,即“不持有任何资源和计数器”。对于移动构造,构造后会让右值参数的_Ptr和_Rep都置为0,实现了“资源的转移”。

这里还用到了一个_Assign_rv函数,这个函数就是把当前对象的_Ptr及_Rep和传入参数对象的_Ptr和_Rep进行互换。该函数定义如下:

void _Assign_rv(_Myt&& _Right)
	// assign by moving _Right
	if (this != &_Right)
		_Swap(_Right);//交换两个对象的_Rep和_Ptr指针

 
void _Swap(_Ptr_base& _Right)   //交换二者的资源指针和引用计数器
	// swap pointers
	_STD swap(_Rep, _Right._Rep);
	_STD swap(_Ptr, _Right._Ptr);

赋值重载

_Ptr_base只定义了移动赋值,这就意味着如果赋值参数非右值,那么_Ptr_base就会使用默认的赋值函数。移动赋值定义如下:

_Myt& operator=(_Myt&& _Right)//右值赋值重载,
			// construct _Ptr_base object that takes resource from _Right
		_Assign_rv(_STD forward<_Myt>(_Right));
		return (*this);
		

这里还是相当于把当前对象的_Ptr和_Rep与传入的参数_Right的_Ptr和_Rep进行交换。

获取引用计数

_Ptr_base中可以通过use_count函数来获取持有资源的引用计数,如下所示。

long use_count() const _NOEXCEPT  //返回引用计数
			// return use count
		return (_Rep ? _Rep->_Use_count() : 0);
		

实际上是调用计数器中的_Use_count函数,返回的是计数器中的_Uses变量。

减少引用计数

_Ptr_base中自己定义了减少引用计数的两种函数,分别用来减少_Ptr_base对应的计数器中的_Uses和_Weaks:

void _Decref()//减少引用次数
			// decrement reference count
		if (_Rep != 0)
			_Rep->_Decref();
		
void _Decwref()//减少弱引用次数
			// decrement weak reference count
		if (_Rep != 0)
			_Rep->_Decwref();
		

可以看到,实际上就是调用相应计数器中的对应函数。

_Reset函数

_Ptr_base中提供了多个_Reset函数的重载版本,如下所示:

void _Reset()  //重置,不持有任何资源以及引用计数器
		// release resource
	_Reset(0, 0);
	
 
template<class _Ty2>
	void _Reset(const _Ptr_base<_Ty2>& _Other)//用其他的_Ptr_base来Reset
		// release resource and take ownership of _Other._Ptr
	_Reset(_Other._Ptr, _Other._Rep); 
	
 
template<class _Ty2>
	void _Reset(const _Ptr_base<_Ty2>& _Other, bool _Throw)
		// release resource and take ownership from weak_ptr _Other._Ptr
	_Reset(_Other._Ptr, _Other._Rep, _Throw);
	
 
template<class _Ty2>
	void _Reset(const _Ptr_base<_Ty2>& _Other, const _Static_tag&)
		// release resource and take ownership of _Other._Ptr
	_Reset(static_cast<_Ty *>(_Other._Ptr), _Other._Rep);
	
 
template<class _Ty2>
	void _Reset(const _Ptr_base<_Ty2>& _Other, const _Const_tag&)
		// release resource and take ownership of _Other._Ptr
	_Reset(const_cast<_Ty *>(_Other._Ptr), _Other._Rep);
	
 
template<class _Ty2>
	void _Reset(const _Ptr_base<_Ty2>& _Other, const _Dynamic_tag&)
		// release resource and take ownership of _Other._Ptr
	_Ty *_Ptr = dynamic_cast<_Ty *>(_Other._Ptr);
	if (_Ptr)
		_Reset(_Ptr, _Other._Rep);
	else
		_Reset();
	
 
template<class _Ty2>
	void _Reset(auto_ptr<_Ty2>&& _Other)
		// release resource and take _Other.get()
	_Ty2 *_Px = _Other.get();
	_Reset0(_Px, new _Ref_count<_Ty>(_Px));
	_Other.release();
	_Enable_shared(_Px, _Rep);
	
 
//为shared_ptr绑定新的资源指针以及_Other中的计数器
//注意,这里的_Other应该是不同类型的shared_ptr,把它的计数器赋给了当前的shared_ptr
template<class _Ty2>
	void _Reset(_Ty *_Ptr, const _Ptr_base<_Ty2>& _Other)//用新的资源指针和_Other的引用计数器来重置
		// release resource and alias _Ptr with _Other_rep
	_Reset(_Ptr, _Other._Rep);//重置为新的资源指针和引用计数管理器
	
	
//为shared_ptr绑定新的资源指针和新的计数器
//如果新的计数器非NULL,计数器就加1,如果本身持有计数器,那么原来的计数器就减1
//注意,_Ref_count_base参数只有计数变量,没有资源指针
void _Reset(_Ty *_Other_ptr, _Ref_count_base *_Other_rep)//重新绑定当前shared_ptr的资源指针和计数器,对原有计数器引用计数-1,新的计数器引用计数+1
		// release resource and take _Other_ptr through _Other_rep
	if (_Other_rep)//如果传入的计数器不为NULL,相当于_Reset了之后当前的shared_ptr就要使用这个计数器对应的资源了,因此该资源就增加一次引用
		_Other_rep->_Incref();
	_Reset0(_Other_ptr, _Other_rep);//重新绑定资源指针和计数器
	
//和上面一样,只是多了一个是否抛出异常的参数
void _Reset(_Ty *_Other_ptr, _Ref_count_base *_Other_rep, bool _Throw)
		// take _Other_ptr through _Other_rep from weak_ptr if not expired
		// otherwise, leave in default state if !_Throw,
		// otherwise throw exception
	if (_Other_rep && _Other_rep->_Incref_nz())//如果引用计数器非null,并且计数值不为0
		_Reset0(_Other_ptr, _Other_rep);
	else if (_Throw)
		_THROW_NCEE(bad_weak_ptr, 0);
	

这么多版本看着吓人,实际上本质是一样的:用其它对象的_Ptr和_Rep来重置当前对象的_Ptr和_Rep,相当于当前对象获取传入对象的资源指针和计数器。从这个本质上来看,肯定会做两件事:减少当前对象持有资源的引用计数_Uses,增加传入对象持有资源的引用计数_Uses

前面的多个重载版本内部调用的实际上是最后的那两个版本:

void _Reset(_Ty *_Other_ptr, _Ref_count_base *_Other_rep);
void _Reset(_Ty *_Other_ptr, _Ref_count_base *_Other_rep, bool _Throw);

注意到,在这两个函数的内部,都会通过调用计数器的_Incref或者_Incref_nz增加传入对象持有资源的引用计数。然后调用的都是同一个_Reset0函数,该函数定义如下:

void _Reset0(_Ty *_Other_ptr, _Ref_count_base *_Other_rep)
			// release resource and take new resource
		if (_Rep != 0)
			_Rep->_Decref();
		_Rep = _Other_rep;  //用传入的两个参数来赋值给_Rep和_Ptr
		_Ptr = _Other_ptr;
		

在该函数中,对当前对象持有资源的引用计数减1,然后将传入对象的两个指针赋值给当前对象。

_Resetw函数

这个函数也提供了多个重载版本,如下所示。

void _Resetw()//初始化weak_ptr的资源指针和计数器都为NULL
			// release weak reference to resource
		_Resetw((_Ty *)0, 0);
		
 
	template<class _Ty2>//用不同类型的weak_ptr或者shared_ptr的资源指针和计数器来更新当前的weak_ptr
		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(const _Ty2 *_Other_ptr, _Ref_count_base *_Other_rep)
			// point to _Other_ptr through _Other_rep
		_Resetw(const_cast<_Ty2*>(以上是关于从源码理解智能指针—— shared_ptrweak_ptr的主要内容,如果未能解决你的问题,请参考以下文章

通过理解函数指针构建回调函数来实现mosquitto源码功能

通过理解函数指针构建回调函数来实现mosquitto源码功能

通过理解函数指针构建回调函数来实现mosquitto源码功能

C++|深入理解智能指针

C++ 智能指针最佳实践&源码分析

智能指针--转