从源码理解智能指针(二)—— shared_ptr、weak_ptr

目录

计数器

_Ref_count

_Ref_count_del

_Ref_count_del_alloc

_Ptr_base

_Ptr_base的成员变量

构造函数

赋值重载

获取引用计数

减少引用计数

_Reset函数

_Resetw函数

shared_ptr

构造函数

无参构造

用一般参数构造

用完整对象构造

移动构造

析构函数

赋值重载

reset函数

获取资源指针

其他的重载

shared_ptr是否独占资源

循环引用计数问题

weak_ptr

构造函数

赋值重载

析构函数

检查资源是否有效

lock函数

总结


       在前面一文中,分析了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只接受用资源指针、删除器实例和分配器实例来构造。

 

      可以看到,计数器实际上是通过_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*>(_Other_ptr), _Other_rep);
		}

	template<class _Ty2>
		void _Resetw(_Ty2 *_Other_ptr, _Ref_count_base *_Other_rep)
		{	// point to _Other_ptr through _Other_rep
		if (_Other_rep)
			_Other_rep->_Incwref();  //新的引用计数器的弱引用计数+1
		if (_Rep != 0)
			_Rep->_Decwref(); //原本的引用计数器的弱引用计数-1
		_Rep = _Other_rep;  //重置资源指针以及引用计数器
		_Ptr = _Other_ptr;
		}

         前面版本的_Resetw函数最终都会调用最后一个版本的_Resetw,在该版本中,和_Reset一样,只不过对象由引用计数变量_Uses变成了弱引用计数变量_Weak,对当前对象的弱引用计数减一,然后对传入对象的引用计数加1。

 

         分析完了_Ptr_base,现在再来看继承自它的shared_ptr和weak_ptr。

shared_ptr

        shared_ptr继承自_Ptr_base,也就有了父类中的资源指针_Ptr和计数器指针_Rep,除此之外,shared_ptr中并未定义任何成员变量,只定义了两种类型_Myt和_Mybase分别为当前shared_ptr和基类的类型,还有一大堆成员函数。

template<class _Ty>
	class shared_ptr
		: public _Ptr_base<_Ty>
{	// class for reference counted resource management
  public:
	 typedef shared_ptr<_Ty> _Myt;
	 typedef _Ptr_base<_Ty> _Mybase;
        ......
}

构造函数

        shared_ptr中提供了许多构造函数,下面把它们分成几个部分讲。

无参构造

       shared_ptr的无参构造函数中什么都没做,不过即使是这样,通过构造基类也使得_Ptr和_Rep均初始化为0。

shared_ptr() _NOEXCEPT     //无参构造
		{	// construct empty shared_ptr
		}

用一般参数构造

        这一类构造函数是指用非完整对象来进行构造,如资源指针、删除器实例和分配器实例中一个或多个组合参数来构造。

	template<class _Ux>
		explicit shared_ptr(_Ux *_Px)    //用资源指针构造
		{	// construct shared_ptr object that owns _Px
		_Resetp(_Px);//用传入的资源指针来初始化资源指针,默认的引用计数管理器是_Ref_count类型,该计数器也和这个资源绑定
		}

	template<class _Ux,
		class _Dx>
		shared_ptr(_Ux *_Px, _Dx _Dt)//用不同类型的资源指针以及删除器来构造
		{	// construct with _Px, deleter
		_Resetp(_Px, _Dt);//默认的引用计数器为_Ref_count_del,其中包含了一个删除器以及资源指针
		}

	shared_ptr(nullptr_t)//nullptr构造
		{	// construct empty shared_ptr
		}

	template<class _Dx>
		shared_ptr(nullptr_t, _Dx _Dt) //用nullptr和删除器实例来构造
		{	// construct with nullptr, deleter
		_Resetp((_Ty *)0, _Dt);
		}

	template<class _Dx,
		class _Alloc>
		shared_ptr(nullptr_t, _Dx _Dt, _Alloc _Ax)//用nullptr、删除器实例、分配器实例来构造
		{	// construct with nullptr, deleter, allocator
		_Resetp((_Ty *)0, _Dt, _Ax);
		}

	template<class _Ux,
		class _Dx,
		class _Alloc>
		shared_ptr(_Ux *_Px, _Dx _Dt, _Alloc _Ax)//用不同类型的资源指针、和删除器及分配器实例构造
		{	// construct with _Px, deleter, allocator
		_Resetp(_Px, _Dt, _Ax);
		}

        这一类构造函数有一个共同点,就是在构造函数内部会调用_Resetp函数,_Resetp函数有三个重载版本,定义如下:

private://以下3个_Resetp在 用资源指针、删除器、分配器三者一个或多个来构造时调用,
        //三者对应三种情况,分配相应的计数器,分配的计数器中的资源指针/删除器、分配器就是传入的资源指针/删除器、分配器
        //初始化后引用计数和弱引用计数均为1
	template<class _Ux>
		void _Resetp(_Ux *_Px)   //只用一个资源指针来构造shared_ptr时会调用该函数,分配一个默认的计数器
		{	// release, take ownership of _Px
		_TRY_BEGIN	// allocate control block and reset   try {
		_Resetp0(_Px, new _Ref_count<_Ux>(_Px));
		_CATCH_ALL	// allocation failed, delete resource   }catch(...){
		delete _Px;
		_RERAISE;    //throw
		_CATCH_END   //end
		}

	template<class _Ux,
		class _Dx>
		void _Resetp(_Ux *_Px, _Dx _Dt)
		{	// release, take ownership of _Px, deleter _Dt
		_TRY_BEGIN	// allocate control block and reset
		_Resetp0(_Px, new _Ref_count_del<_Ux, _Dx>(_Px, _Dt));
		_CATCH_ALL	// allocation failed, delete resource
		_Dt(_Px);
		_RERAISE;
		_CATCH_END
		}

	template<class _Ux,
		class _Dx,
		class _Alloc>
		void _Resetp(_Ux *_Px, _Dx _Dt, _Alloc _Ax)
		{	// release, take ownership of _Px, deleter _Dt, allocator _Ax
		typedef _Ref_count_del_alloc<_Ux, _Dx, _Alloc> _Refd;
		typename _Alloc::template rebind<_Refd>::other _Al = _Ax;

		_TRY_BEGIN	// allocate control block and reset
		_Refd *_Ptr = _Al.allocate(1);
		::new (_Ptr) _Refd(_Px, _Dt, _Al);
		_Resetp0(_Px, _Ptr);
		_CATCH_ALL	// allocation failed, delete resource
		_Dt(_Px);
		_RERAISE;
		_CATCH_END
		}

       _Resetp函数的三种形式,分别对应了三种计数器,每一种_Resetp函数中都会用相应的计数器实例作为参数来调用_Resetp0函数,_Resetp0函数定义如下:

template<class _Ux>
		void _Resetp0(_Ux *_Px, _Ref_count_base *_Rx)
		{	// release resource and take ownership of _Px
		this->_Reset0(_Px, _Rx); //用_Px和_Rx进行重置
		_Enable_shared(_Px, _Rx);
		}

        这里的_Reset0函数是在基类_Ptr_base中定义的,用来设置_Ptr和_Rep。到这里,这一类构造函数的构造过程也就清楚了,举个例子:如果构造时只用一个资源指针_Ptr构造,那么就会通过_Resetp分配一个_Ref_count计数器实例,然后用传入的资源指针_Ptr和_Ref_count来构造当前shared_ptr对象;如果构造时还传入了删除器,同理就分配_Ref_count_del计数器实例,然后进行构造;如果构造时还传入了分配器,就用_Ref_count_del_alloc来构造。不管是哪种构造方式,构造结束后计数器中的计数变量都为1。

       不管是什么情况,shared_ptr对象一开始不是从无参构造得到,就是通过这类构造函数得到,从_Resetp的几个版本函数中都可以看到,shared_ptr本身持有的_Ptr和与之绑定的计数器的_Ptr是相同的,也就是说,在正常情况下,shared_ptr和它所对应的计数器所持有的资源指针,是相同的。并且,计数器所对应的资源指针,一经构造,则不可更改

用完整对象构造

        这一类构造函数是通过完整的对象来构造,比如用shared_ptr对象、weak_ptr甚至auto_ptr对象来构造,如下所示:

template<class _Ty2>
		shared_ptr(const shared_ptr<_Ty2>& _Right, _Ty *_Px) _NOEXCEPT
		{	// construct shared_ptr object that aliases _Right
		this->_Reset(_Px, _Right); //把_Right的计数器和_Px作为当前shared_ptr的新的资源指针和计数器,_Right的计数器并不会改变资源指向
		}

	shared_ptr(const _Myt& _Other) _NOEXCEPT//同类型拷贝构造,让当前shared_ptr绑定到_Other的资源指针和计数器
		{	// construct shared_ptr object that owns same resource as _Other
		this->_Reset(_Other);//当前shared_ptr和_Other共享计数器和资源
		}
	//用不同类型的shared_ptr来进行构造,如果_Ty2 *不能与_Ty *相互转换,那么enable_if就不存在type成员,编译自然报错
	template<class _Ty2,
		class = typename enable_if< is_convertible<_Ty2 *, _Ty *>::value,void>::type>
		shared_ptr(const shared_ptr<_Ty2>& _Other) _NOEXCEPT//不同类型的shared_ptr拷贝构造
		{	// construct shared_ptr object that owns same resource as _Other
		this->_Reset(_Other);//共享计数器和资源
		}
	//用weak_ptr来构造shared_ptr,如果成功,二者共享资源指针以及计数器,否则抛出异常
	template<class _Ty2>
		explicit shared_ptr(const weak_ptr<_Ty2>& _Other,
			bool _Throw = true)
		{	// construct shared_ptr object that owns resource *_Other
		this->_Reset(_Other, _Throw);
		}

//直接用_Other进行强制转换来构造,第二个参数指定使用const_cast转换
	template<class _Ty2>
		shared_ptr(const shared_ptr<_Ty2>& _Other, const _Const_tag& _Tag)
		{	// construct shared_ptr object for const_pointer_cast
		this->_Reset(_Other, _Tag);
		}
	//_Dynamic_cast转换
	template<class _Ty2>
		shared_ptr(const shared_ptr<_Ty2>& _Other, const _Dynamic_tag& _Tag)
		{	// construct shared_ptr object for dynamic_pointer_cast
		this->_Reset(_Other, _Tag);

        这一类构造函数实质上就是调用_Ptr_base中的_Reset函数,相当于是用传入的完整对象的资源指针和计数器指针来初始化构造对象的资源指针和计数器指针,既然调用了_Reset函数,那么传入的对象资源的引用计数就必定会加1。

移动构造

       shared_ptr除了使用shared_ptr来移动构造,还支持使用auto_ptr和unique_ptr来移动构造,在移动构造之后,传入的对象就不应持有资源了。

template<class _Ty2>
		shared_ptr(auto_ptr<_Ty2>&& _Other)
		{	// construct shared_ptr object that owns *_Other.get()
		this->_Reset(_STD move(_Other));
		}
shared_ptr(_Myt&& _Right) _NOEXCEPT
		: _Mybase(_STD forward<_Myt>(_Right))
		{	// construct shared_ptr object that takes resource from _Right
		}
	//用不同类型的shared_ptr来移动构造,也需要保证_Ty2 *能和_Ty *进行转换
	template<class _Ty2,
		class = typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
			void>::type>
		shared_ptr(shared_ptr<_Ty2>&& _Right) _NOEXCEPT
		: _Mybase(_STD forward<shared_ptr<_Ty2> >(_Right))
		{	// construct shared_ptr object that takes resource from _Right
		}

	template<class _Ux,
		class _Dx>	//用unique_ptr来移动构造,获取其资源指针以及删除器,并将_Right的资源指针设置为NULL
		shared_ptr(unique_ptr<_Ux, _Dx>&& _Right)
		{	// construct from unique_ptr
		_Resetp(_Right.release(), _Right.get_deleter());
		}

         可以看到在这一部分的构造函数中,多处用到move/forward,这其是都是为了保持参数的右值型,让这些参数在内部调用时也能以右值的类型传入。移动构造相当于把参数对象的资源指针和计数器指针转移到构造对象上,资源的引用计数应当不变。

析构函数

~shared_ptr() _NOEXCEPT
		{	// release resource
		this->_Decref(); //如果引用计数为0,则在计数器类中进行资源释放
		}

        shared_ptr析构函数实际上就是调用计数器的_Decref函数,减少持有资源的引用计数。 

赋值重载

       shared_ptr提供以下赋值操作:

template<class _Ux,
		class _Dx>
		_Myt& operator=(unique_ptr<_Ux, _Dx>&& _Right)
		{	// move from unique_ptr
		shared_ptr(_STD move(_Right)).swap(*this);
		return (*this);
		}
	//用shared_ptr来移动赋值,移动构造一个临时对象然后进行交换
	_Myt& operator=(_Myt&& _Right) _NOEXCEPT
		{	// construct shared_ptr object that takes resource from _Right
		shared_ptr(_STD move(_Right)).swap(*this);
		return (*this);
		}
	//用不同类型的shared_ptr移动赋值,移动构造一个临时对象,然后用临时对象的资源指针和计数器指针来交换
	template<class _Ty2>//不同类型的shared_ptr会在构造临时对象时进行检查是否能转换类型
		_Myt& operator=(shared_ptr<_Ty2>&& _Right) _NOEXCEPT
		{	// construct shared_ptr object that takes resource from _Right
		shared_ptr(_STD move(_Right)).swap(*this);
		return (*this);
		}

_Myt& operator=(const _Myt& _Right) _NOEXCEPT
		{	// assign shared ownership of resource owned by _Right
		shared_ptr(_Right).swap(*this);//之所以要构造一个临时对象,是为了增加计数器,并且交换之后能够把从*this那得来的计数器减1
		return (*this);
		}
	//用不同类型的shared_ptr进行赋值,类型是否能相互转换会在构造临时对象中判断,
	//临时对象构造成功后,会把传入的_Right的引用计数+1,交换后持有*this的计数器
	//函数退出时临时对象析构,会把*this原来的计数器引用计数-1
	template<class _Ty2>
		_Myt& operator=(const shared_ptr<_Ty2>& _Right) _NOEXCEPT
		{	// assign shared ownership of resource owned by _Right
		shared_ptr(_Right).swap(*this);
		return (*this);
		}
	//用不同类型的auto_ptr来移动赋值,这里使用临时对象可以判断类型是否能转换,以及析构时对*this的原计数器自动计数-1
	template<class _Ty2>
		_Myt& operator=(auto_ptr<_Ty2>&& _Right)
		{	// assign ownership of resource pointed to by _Right
		shared_ptr(_STD move(_Right)).swap(*this);
		return (*this);
		}

       这些赋值函数内部实现都有一个共同点:先用参数对象构造一个临时的shared_ptr对象,然后再将当前对象与这个临时对象swap。swap函数作用,就是交换当前对象和临时对象的_Ptr和_Rep。那么,为什么要这么做呢?

       既然要实现赋值,那么就需要有三件事需要做:将参数对象的_Ptr和_Rep赋值给当前对象、增加参数对象持有资源的引用计数、减少当前对象持有资源的引用计数。只有完成这三步,才能真正实现shared_ptr的赋值。

用参数对象构造一个临时对象,这就相当于让参数对象持有资源的引用计数加1;

临时对象的_Ptr和_Rep就是参数对象的_Ptr和_Rep, 通过临时对象和当前对象swap,相当于把_Ptr和_Rep赋值给当前对象;

临时对象是一个栈上对象,赋值函数结束后会自动析构临时对象,临时对象析构会让原来的参数对象的持有资源引用计数减1。

       也就是说,通过shared_ptr().swap(*this),则可以完成以上三个步骤。

reset函数

       shared_ptr也有reset函数,通过reset函数,相当于重新构造了shared_ptr。

void reset() _NOEXCEPT    //把当前的shared_ptr重置为空,即不持有任何资源和计数器
		{	// release resource and convert to empty shared_ptr object
		shared_ptr().swap(*this);
		}

	template<class _Ux>
		void reset(_Ux *_Px)
		{	// release, take ownership of _Px
		shared_ptr(_Px).swap(*this);
		}

	template<class _Ux,
		class _Dx>
		void reset(_Ux *_Px, _Dx _Dt)
		{	// release, take ownership of _Px, with deleter _Dt
		shared_ptr(_Px, _Dt).swap(*this);
		}

	template<class _Ux,
		class _Dx,
		class _Alloc>
		void reset(_Ux *_Px, _Dx _Dt, _Alloc _Ax)
		{	// release, take ownership of _Px, with deleter _Dt, allocator _Ax
		shared_ptr(_Px, _Dt, _Ax).swap(*this);

获取资源指针

       shared_ptr通过get函数来获取资源指针,其内部实际上调用的是父类中的_Get函数,用来获取父类中定义的_Ptr。

_Ty *get() const _NOEXCEPT    //获取资源指针
		{	// return pointer to resource
		return (this->_Get());
		}

其他的重载

typename add_reference<_Ty>::type operator*() const _NOEXCEPT
		{	// return reference to resource
		return (*this->_Get());
		}

	_Ty *operator->() const _NOEXCEPT
		{	// return pointer to resource
		return (this->_Get());
		}

	explicit operator bool() const _NOEXCEPT
		{	// test if shared_ptr object owns no resource
		return (this->_Get() != 0);
		}

       和unique_ptr类似,重载“*”和“->”让shared_ptr的行为更像指针,shared_ptr对象的布尔判断,实际上是判断其资源指针是否为nullptr。

shared_ptr是否独占资源

       通过unique函数可以检查当前对象是否是唯一拥有资源的shared_ptr,它是通过判断引用计数是否为1来实现的。

bool unique() const _NOEXCEPT
		{	// return true if no other shared_ptr object owns this resource
		return (this->use_count() == 1);
		}

循环引用计数问题

       通过以上对shared_ptr的分析可以知道,多个shared_ptr可以持有同一资源,同一资源每被一个shared_ptr持有,相应的引用计数就会加1,当shared_ptr对象析构时,持有资源并不一定就会一同被销毁,而只是减少了引用计数而已。引用计数的存在,使得多个shared_ptr可以同时持有一个资源,但同时也是因为引用计数的存在,也会引起相应的问题。

       现在假设有4个shared_ptr的对象A、B、C、D,A和B分别持有资源M和N,而C和D恰好又分别属于资源M和N,更奇葩的是,C持有资源N而D持有N,这样就构成了以下情况:

       为了正常释放资源M,那么对象A和对象D就必须正常析构,而对象D的析构的前提是资源N被正常释放,这就相当于资源M的释放前提是资源N的释放;

       同样,为了正常释放资源N,那么对象B和对象C就必须正常析构,而对象C的析构前提是资源M被正常释放,这就相当与资源N的释放前提是资源N的释放;

       这样就引起了类似于死锁的问题:资源M需要资源N释放才能释放,资源N需要资源M释放才能释放,最后的结果,就是资源M和资源N都不能释放,用代码来描述如下所示:

class B
{
public:
	B()
	{
		cout << "B()" << endl;
	}
	~B()
	{
		cout << "~B()" << endl;
	}

	std::shared_ptr<B>pb;
};

int _tmain(int argc, _TCHAR* argv[])
{
	std::shared_ptr<B>p1(new B());  

	std::shared_ptr<B>p2(new B());

	p1->pb = p2;

	p2->pb = p1;
}

       可见,创建的两处B资源并未正常释放,引起了内存泄漏。

       这个问题的根本,就是位于资源M中的对象C持有了资源N,这样就导致资源N的释放的前提是B和C都析构,资源M同理。从程序中可以看到,对象A和B(对应于p1和p2)是不会依赖于其他资源的,如果资源N可以不受对象C的影响,那么在对象B析构时,资源N就可以释放了,其本质还是因为对象C持有资源N时增加了资源N的引用计数。

       那么有没有什么办法既可以让对象C能够持有资源N,又不会增加资源N的引用计数呢?其实通过裸指针完全可以实现,比如C被申明为B*类型而不是shared_ptr对象,那么直接让C指向资源N,这样就可以了。

       另一个方法则是使用weak_ptr。weak_ptr既然存在,那么自然就有其相较于裸指针的优势,下面就先来分析一下weak_ptr。

weak_ptr

       weak_ptr也是继承于_Ptr_base,不过它的定义比shared_ptr简单多了。

构造函数

       weak_ptr只能通过其它的weak_ptr对象或者shared_ptr对象来构造,如下所示:

weak_ptr() _NOEXCEPT   //无参构造
		{	// construct empty weak_ptr object
		}

	weak_ptr(const weak_ptr& _Other) _NOEXCEPT  //用weak_ptr进行构造,和shared_ptr类似的
		{	// construct weak_ptr object for resource pointed to by _Other
		this->_Resetw(_Other);
		}

	template<class _Ty2,
		class = typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
			void>::type>
		weak_ptr(const shared_ptr<_Ty2>& _Other) _NOEXCEPT  //用shared_ptr来构造
		{	// construct weak_ptr object for resource owned by _Other
		this->_Resetw(_Other); //重置后当前weak_ptr的弱引用计数-1,_Other的弱引用计数+1,
		}

	template<class _Ty2,
		class = typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
			void>::type>
		weak_ptr(const weak_ptr<_Ty2>& _Other) _NOEXCEPT
		{	// construct weak_ptr object for resource pointed to by _Other
		this->_Resetw(_Other.lock());//
		}

      从构造函数的限制上就能看出,weak_ptr是依赖于shared_ptr对象的,你不能不依赖其它的shared_ptr来创建一个weak_ptr(无参构造除外)。这些构造函数最终都是调用_Resetw函数,_Resetw函数在前面_Ptr_base中也有提到,获取参数对象的_Ptr和_Rep,然后增加、减少相应对象的弱引用计数

赋值重载

weak_ptr& operator=(const weak_ptr& _Right) _NOEXCEPT
		{	// assign from _Right
		this->_Resetw(_Right);
		return (*this);
		}

	template<class _Ty2>
		weak_ptr& operator=(const weak_ptr<_Ty2>& _Right) _NOEXCEPT
		{	// assign from _Right
		this->_Resetw(_Right.lock()); //lock()中构造shared_ptr时会检查类型是否相容,通过lock()还可以检查传入参数的引用计数是否为0,如果为0就用空的shared_ptr构造
		return (*this);
		}

	template<class _Ty2>
		weak_ptr& operator=(const shared_ptr<_Ty2>& _Right) _NOEXCEPT
		{	// assign from _Right
		this->_Resetw(_Right);
		return (*this);
		}

       由此可以看到,weak_ptr也是可以进行赋值拷贝的,但是它并不会像shared_ptr那样用_Reset来增加引用计数,而是通过_Resetw来增加或减少弱引用计数。

析构函数

      weak_ptr的析构函数作用就是减少资源的弱引用次数,如前所述,当资源引用此时为0时会销毁资源,而当弱引用次数为0时会销毁计数器对象。由于weak_ptr需要shared_ptr才能构造,所以同一资源弱引用计数肯定不会小于引用计数,如果弱引用此时都已经为0了,说明没有任何weak_ptr再持有这个资源,此时也不可能有shared_ptr持有这个资源,此时的计数器也没有任何意义,直接销毁计数器即可。

~weak_ptr() _NOEXCEPT  //减少弱引用计数,
		{	// release resource
		this->_Decwref();			
		}

检查资源是否有效

       weak_ptr的一个优势,就是可以随时查看它所持有的资源是否有效。这里的有效是指资源还未被释放。这一点对于普通的裸指针或者shared_ptr来说是完全做不到的。裸指针只是一个地址,无法通过这个地址知道它是否已经被释放,而对于shared_ptr,它自身的存在就会影响资源,所以通过shared_ptr来检查资源是否有效是不现实的。而对于weak_ptr来说,它只是持有资源,但是并不会影响资源的释放,通过expired函数可以获取计数器中的引用计数变量,这样就可以知道资源是否还有效。

bool expired() const _NOEXCEPT  //失效是引用计数为0
		{	// return true if resource no longer exists
		return (this->_Expired());
		}

bool _Expired() const//为true则表明资源已失效
		{	// test if expired
		return (!_Rep || _Rep->_Expired());
		}

lock函数

      weak_ptr还有一个很重要的lock()函数,其定义如下:

shared_ptr<_Ty> lock() const _NOEXCEPT
		{	// convert to shared_ptr
		return (shared_ptr<_Ty>(*this, false));//用当前weak_ptr构造shared_ptr,计数器不变,如果weak_ptr对应的计数器引用计数已经为0了,那么返回得到的就是一个无参构造的shared_ptr
		}

      这个函数会使用weak_ptr的资源指针和计数器去构造一个shared_ptr,如果计数器中引用计数为0,那么返回的shared_ptr就是一个无参构造的shared_ptr。

       该函数的返回值类型为shared_ptr类型,而shared_ptr重载了operator bool,所以还可以通过if( weak_ptr.lock())来判断资源是否被释放。如果资源已经被释放,那么lock的返回值就是一个_Ptr为0的shared_ptr,bool值就是false。

        如果前面的expired函数为false,说明资源还未被释放,那么通过lock函数还可以重新获得一个持有该资源的shared_ptr,相当于可以锁住资源,防止资源被释放。需要注意的是,如果通过weak_ptr重新提升为了一个shared_ptr,那么说明本身资源就有效的,不然不可能提升成功,也就是说,提升之后资源的引用计数至少为2。

总结

      shared_ptr和weak_ptr的结构大致如下所示:

      shared_ptr是共享式指针,多个对象可以持有同一个资源,只有当最后一个持有该资源的对象析构时,才会释放资源;

      与shared_ptr相比,weak_ptr是一种功能不完整的“智能指针”,从weak_ptr只能通过shared_ptr来构造,并且连基本的“*”和“->”都没有重载就可以看出来,weak_ptr实际上就是用来辅助shared_ptr的工具,不能单独使用。它也会持有资源,但它并不会影响资源的引用计数,说起来裸指针也能持有资源并不会影响资源的引用计数。而对于weak_ptr来说,它的优势在于,它可以通过expired来检查资源的有效性(即是否被释放),在需要的时候可以通过lock转换为shared_ptr来使用资源,这是裸指针所做不到的;

      应当遵从RAII原则,资源的获取时刻就是智能指针的初始化,而不应当用原生指针去构造shared_ptr对象,如以下所示:

int * p = new A();

std::shared_ptr<A>sp(p);   //应当直接std::shared_ptr<A>sp(new A());

       这样容易引起两个问题:1.指针p暴露在外面,这是智能指针所不允许的,因为随时有可能在某个地方delete p;从而影响智能指针的管理;2.一不小心就会用p去构造多个shared_ptr,如std::shared_ptr<A>sp(p);std::shared_ptr<A>sp1(p);sp和sp1都用p来构造,按理来说应当是p这个资源的引用计数为2,但实际上不是的,因为每当一个shared_ptr通过非完整对象构造出来,都会分配一个新的计数器给它,这就相当于sp和sp1的计数器是不同的,相当于有两个计数器管理同一资源,当sp析构,导致sp对应的计数器的引用计数为0,此时就会释放资源p,等到sp1析构的时候,又会使得sp1对应的计数器引用计数为0,此时又会释放资源p,这样就引起了多次析构同一资源。为了避免这一问题,就应当遵从RAII原则,避免原生指针和智能指针同时存在

       遵从RAII原则的另一种构造shared_ptr的方式,是使用make_shared函数,该函数传入的参数就是构造相应资源的参数,返回一个管理该资源的shared_ptr;

       非完整对象构造shared_ptr(如只使用资源指针或删除器、分配器来构造)时会分配一个新的计数器实例,而其它构造方式以及赋值则只是改变计数器及其计数,而不会分配新的计数器;

       如果一个shared_ptr对象位于某一资源内部,而该资源被其它对象所持有,就需要考虑循环引用的问题,是否需要转换weak_ptr;

       shared_ptr虽然有它好的地方,但是由于计数器类的存在,会导致其开销比普通指针大得多,因此绝不应该滥用shared_ptr,相比之下unique_ptr的开销更小,如果能用unique_ptr解决的问题就不要用shared_ptr。

       

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章