从源码理解智能指针—— shared_ptrweak_ptr
Posted 贺二公子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从源码理解智能指针—— shared_ptrweak_ptr相关的知识,希望对你有一定的参考价值。
原文地址:https://blog.csdn.net/qq_28114615/article/details/101166744
文章目录
在前面一文中,分析了auto_ptr和unique_ptr两种智能指针,二者都是独占式指针,即资源只能由一个智能指针拥有并管理。本文就来分析一下另外两种非独占式智能指针——共享式指针shared_ptr和weak_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中主要包含以下信息:
- 两个纯虚函数_Destroy()和_Delete_this(),意味着二者必须在子类中实现,前者用来释放计数器对应的资源,后者用来释放计数器自身;
- 两个计数变量_Uses和_Weaks,前者是强引用计数,主要用于shared_ptr中,后者用于weak_ptr中;
- 定义了对_Uses和_Weaks的增加、减少函数。如果某一次减少使得_Uses为0,那么就会调用子类中实现的_Destroy函数并且减少_Weaks;如果某一次减少使得_Weaks为0,就会调用子类中实现的_Delete_this函数;
- _Ref_count_base的构造会初始化_Uses和_Weaks为1;
- _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; //资源指针
;
该类中主要包含以下信息:
- _Ref_count中含有一个资源指针_Ptr,指向该计数器对应的资源;
- _Ref_count从_Ref_count_base_继承来的计数变量相关操作,都是针对_Ptr的;
- _Ref_count类中必须重写_Destroy和_Delete_this函数,前者对_Ptr进行释放,后者用来析构当前计数器对象。由于_Ref_count_base_的析构函数是虚函数,因此_Ref_count可以完全析构;
- _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
;
该类主要包含以下信息:
- _Ref_count_del除了有一个资源指针_Ptr,还有一个_Dx类型的删除器实例_Dtor;
- _Ref_count_del从_Ref_count_base_继承来的计数变量相关操作,都是针对_Ptr的;
- 重写的_Destroy函数中,并不像_Ref_count那样直接调用delete删除,而是使用的_Dtor(_Ptr),这说明删除器必须是一个仿函数类,_Delete_this和上述相同;
- _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
该类主要包含以下信息:
- _Ref_count_del_alloc增加了一个分配器实例;
- _Ref_count_del_alloc从_Ref_count_base_继承来的计数变量相关操作,都是针对_Ptr的;
- 重写的_Destroy函数中,用仿函数类实例_Dtor来销毁资源指针,而在重写的_Delete_this中,则是通过分配器实例来进行的,可见,分配器中必须实现destroy函数和deallocate函数(这就很像STL中的allocator);
- _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源码功能