std::tr1::shared_ptr源码赏析

vs2010的std::tr1::shared_ptr和boost的shared_ptr比起来简单易懂,代码短小精悍。我没有细读过boost::shared_ptr的代码,粗略看过,细节上的区别还是很大的。

大致结构

shared_ptr是一个模板类,派生自_Ptr_base。其中_Ptr_base内记录的_Ty*是用户创建shared_ptr时传入的要管理的指针;而_Ptr_base内还有一个成员变量,是一个_Ref_count_base的指针。它是实现引用计数功能的主力。


关键的_Ref_count_base

_Ref_count_base 是在shared_ptr创建的时候new出来的。

_Ref_count_base有3个子类,分别对应shared_ptr构造时的3种形式。在shared_ptr的实现中,会根据调用的shared_ptr构造函数的不同,使用不同类型的_Ref_count_base创建。

_Ref_count_base中保存了两个引用计数:

l _Uses:用于shared_ptr,表示该_Ref_count_base被几个shared_ptr引用了。当_Uses等于0时,会释放_Ref_count_base中保存的对象(也就是用户传入的指针)。

l _Weaks:用于weak_ptr,表示该_Ref_count_base被几个weak_ptr引用了。当_ Weaks等于0时,会释放_Ref_count_base自身。

有了这两个引用计数,就能实现只能指针管理资源基本功能以及野指针检查的功能。

构造函数中奇怪的参数

在shared_ptr和weak_ptr的构造函数中均有几个显眼的重载项,例如:

template<class _Ty2>

shared_ptr(const shared_ptr<_Ty2>& _Other,

typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,

void *>::type * = 0)

{ // construct shared_ptr object that owns same resource as _Other

this->_Reset(_Other);

}

这里enable_if<is_convertible<_Ty2 *, _Ty *>::value, void *>::type * = 0 这个参数究竟表达什么意思呢?

这其实是一个模版元编程的一个惯用手法,即type_traits。利用编译期推导的特性,让编译器选择何时重载的函数。

这里的意思就是如果出现这样的代码:

class A{};

class B:public A{};

shared_ptr<A> pA(new A);

shared_ptr<B> pB(pA);

当执行shared_ptr<B> pB(pA);时会走上面的函数。换句话说,如果B和A没有派生关系,那么编译器就找不到对应的重载函数,就会在编译期提示错误。

OK,深入到细节仔细看一下这个type_traits的实现。其中,is_convertible<_Ty2 *, _Ty *>会被展开为以下代码:

template<bool>

struct _Cat_base;

template<>

struct _Cat_base<false>

: false_type

{ // base class for type predicates

};

template<>

struct _Cat_base<true>

: true_type

{ // base class for type predicates

};

template<class _From, class _To>

struct is_convertible :

_Cat_base<is_void<_From>::value &&

is_void<_To>::value ||

__is_convertible_to(_From, _To)>

{ // determine whether _From is convertible to _To

};

其中__is_convertible_to是一个编译器内部支持的函数,能够获取到两个类之间是否能够转换。详见http://technet.microsoft.com/zh-cn/library/ms177194(v=vs.80)。当然这里还有两种特殊情况,就是如果_From和_To如果都是void类型那么也是可以转换的。

_Cat_base在这里只起到一个把多个布尔值组合起来的功能。_Cat_base有1个泛型实现有2个特化版本。

再看enable_if的实现,手法和_Cat_base如出一辙。实现偏特化的类让编译器选择。

// TEMPLATE CLASS enable_if

template<bool _Test,

class _Type = void>

struct enable_if

{ // type is undefined for assumed !_Test

};

template<class _Type>

struct enable_if<true, _Type>

{ // type is _Type for _Test

typedef _Type type;

};

也就是说只有当is_convertible求值为_Cat_base<true>才可能让编译器推导出enable_if<is_convertible<_Ty2 *, _Ty *>::value,void *>::type。

shared_ptr创建过程

选取一个带有删除器的构造函数作为例子:

template<class _Ux,

class _Dx>

shared_ptr(_Ux *_Px, _Dx _Dt)

{ // construct with _Px, deleter

_Resetp(_Px, _Dt);

}

在该构造函数中,转发调用下面的函数:

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

}

可以看出,在该函数中new了一个_Ref_count_del的类。_Resetp0这个函数是基类_Ptr_base的一个成员函数:

template<class _Ux>

void _Resetp0(_Ux *_Px, _Ref_count_base *_Rx)

{ // release resource and take ownership of _Px

this->_Reset0(_Px, _Rx);

_Enable_shared(_Px, _Rx);(空实现)

}

在这个函数中调用了_Ref_count_base的_Reset0函数,同时还有一个_Enable_shared函数,不过_Enable_shared函数只有当_Px派生自enable_shared_from_this的类有用,对于一般类而言是空实现。

接着看一下构造过程的终点:_Ref_count_base:: 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;

_Ptr = _Other_ptr;

}

很简单的赋值操作,引用计数实现的惯用手法。如果引用计数已经有值的话需要先减去引用计数。下面看减去引用计数的操作:

void _Decref()

{ // decrement use count

if (_MT_DECR(_Mtx, _Uses) == 0)

{ // destroy managed resource, decrement weak reference count

_Destroy(); //纯虚函数

_Decwref();

}

}

这里如果引用计数减到0那么就调用_Destroy函数和_Decwref函数。这里_Destroy函数是_Ref_count_base的一个纯虚函数。该纯虚函数的意图是:如何删除引用计数管理的对象。很容易想象,有删除器的实现自然是调用删除器来完成这个动作,没有删除器的话就调用delete。

再看_Decwref函数。这个函数和weak_ptr有关系,后面讲。实现可以先看下:

void _Decwref()

{ // decrement weak reference count

if (_MT_DECR(_Mtx, _Weaks) == 0)

_Delete_this();

}

_Delete_this函数也是_Ref_count_base的一个纯虚函数。该纯虚函数的意图是:释放自身的方式(即_Ref_count_base)。

shared_ptr的析构过程

~shared_ptr()

{ // release resource

this->_Decref();

}

调用_Ptr_base:: _Decref()函数:

void _Decref()

{ // decrement reference count

if (_Rep != 0)

_Rep->_Decref();

}

调用_Ref_count_base:: _Decref()函数:

void _Decref()

{ // decrement use count

if (_MT_DECR(_Mtx, _Uses) == 0)

{ // destroy managed resource, decrement weak reference count

_Destroy(); //纯虚函数

_Decwref();

}

}

接下来的过程就和前文中提到的shared_ptr构造时的过程一模一样了。

从shared_ptr的析构过程可以看出来。由于weak_ptr引用计数的原因,有可能当shared_ptr析构了以后,对应的管理的对象也被释放了,但是_Ref_count_base却还在内存中留存。一定要等到所有的weak_ptr都释放以后才会彻底释放。

shared_ptr拷贝构造过程

拷贝构造的过程其实和构造函数流程基本一致:

shared_ptr(const _Myt& _Other)

{ // construct shared_ptr object that owns same resource as _Other

this->_Reset(_Other);

}

到这里就会发现拷贝构造的过程和构造过程已经一样了:

template<class _Ty2>

void _Reset(const _Ptr_base<_Ty2>& _Other)

{ // release resource and take ownership of _Other._Ptr

_Reset(_Other._Ptr, _Other._Rep, false);

}

shared_ptr右值拷贝构造过程

shared_ptr(_Myt&& _Right)

: _Mybase(_STD forward<_Myt>(_Right))

{ // construct shared_ptr object that takes resource from _Right

}

这里直接调用的是基类的rvalue拷贝构造:

template<class _Ty2>

_Ptr_base(_Ptr_base<_Ty2>&& _Right)

: _Ptr(_Right._Ptr), _Rep(_Right._Rep)

{ // construct _Ptr_base object that takes resource from _Right

_Right._Ptr = 0;

_Right._Rep = 0;

}

由于_Ptr_base里面也没什么内存块,所以这里的右值拷贝基本和普通拷贝没什么区别。

shared_ptr赋值的过程

赋值的过程,调用拷贝构造然后swap:

template<class _Ty2>

_Myt& operator=(const shared_ptr<_Ty2>& _Right)

{ // assign shared ownership of resource owned by _Right

shared_ptr(_Right).swap(*this);

return (*this);

}

首先是把参数临时拷贝了一份,然后用 STL中随处可见的swap大法。保证异常安全。

void swap(_Myt& _Other)

{ // swap pointers

this->_Swap(_Other);

}

这里调用的是基类_Ptr_base的函数:

void _Swap(_Ptr_base& _Right)

{ // swap pointers

_STD swap(_Rep, _Right._Rep);

_STD swap(_Ptr, _Right._Ptr);

}

_STD是一个宏,展开就是std::。这里调用的是std::swap函数,该函数在utility.h:

// TEMPLATE FUNCTION swap (from <algorithm>)

template<class _Ty> inline

void swap(_Ty& _Left, _Ty& _Right)

{ // exchange values stored at _Left and _Right

_Ty _Tmp = _Move(_Left);

_Left = _Move(_Right);

_Right = _Move(_Tmp);

}

再看_Move函数:

// TEMPLATE FUNCTION _Move

template<class _Ty> inline

typename tr1::_Remove_reference<_Ty>::_Type&&

_Move(_Ty&& _Arg)

{ // forward _Arg as movable

return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);

}

到这里也就仅仅是把右值引用拿过来,通过分析得知,基类_Ptr_base的swap中模板推导参数就应该是_Ref_count_base。所以swap函数中实际会调用_Ref_count_base的右值=,不过_Ref_count_base并没有定义该操作符,所以最终也就是普通的指针赋值了。

weak_ptr大致结构

weak_ptr同样是派生自_Ptr_base,weak_ptr本身没有任何成员变量。但是在_Ref_count_base中保存了一个专门用于weak_ptr的引用计数。

weak_ptr构造的过程

构造过程和shared_ptr也差不多:

template<class _Ty2>

weak_ptr(const shared_ptr<_Ty2>& _Other,

typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,

void *>::type * = 0)

{ // construct weak_ptr object for resource owned by _Other

this->_Resetw(_Other);

}

这里稍微一个不同的地方是,这里用了一个type_traits:enable_if。但是实际上这个参数并没有作用,也没有重载的作用。

接下来就是调用基类的_Resetw函数(这里的w值得就是weak_ptr的意思):

template<class _Ty2>

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(_Ty2 *_Other_ptr, _Ref_count_base *_Other_rep)

{ // point to _Other_ptr through _Other_rep

if (_Other_rep)

_Other_rep->_Incwref();

if (_Rep != 0)

_Rep->_Decwref();

_Rep = _Other_rep;

_Ptr = _Other_ptr;

}

这里并没有判断传入的_Other_ptr是不是等于自身的_Ptr。而是字节先增加引用计数然后再把原本的类成员变量记录的weak_ptr的引用计数减去。

可以很明显的看到。每个shared_ptr构造的时候都会new一个_Ref_count_base出来,但是weak_ptr就不会了。weak_ptr只能通过shared_ptr构造,直接把shared_ptr的_Ref_count_base拿出来用了。

weak_ptr的析构过程

~weak_ptr()

{ // release resource

this->_Decwref();

}

void _Decwref()

{ // decrement weak reference count

if (_Rep != 0)

_Rep->_Decwref();

}

void _Decwref()

{ // decrement weak reference count

if (_MT_DECR(_Mtx, _Weaks) == 0)

_Delete_this();

}

weak_ptr析构的时候,会将自身引用的_Ref_count_base的weak引用计数-1。在_Ref_count_base的实现中,如果weak引用计数等于0,那么就删除自身(_Ref_count_base)。

weak_ptr是怎么实现野指针检测的

这个问题实际上很简单:weak_ptr使用expired函数判断shared_ptr是否已经失效了:

bool expired() const

{ // return true if resource no longer exists

return (this->_Expired());

}

调用的是基类的_Expired:

bool _Expired() const

{ // test if expired

return (!_Rep || _Rep->_Expired());

}

调用_Ref_count_base的_Expired():

bool _Expired() const

{ // return true if _Uses == 0

return (_Uses == 0);

}

可以看到,实际上检查shared_ptr是不是已经被释放掉了是通过检查_Ref_count_base来实现的。也就是说_Ref_count_base中的两个引用计数都等于0时,_Ref_count_base自己才会被释放掉。

enable_shared_from_this是如何工作的

首先要说明一个 enable_shared_from_this的坑:

1、 一个类C派生自enable_shared_from_this,那么不能在类C的构造函数中调用shared_from_this()。

2、 一个类C派生自enable_shared_from_this,如果需要使用shared_from_this这个功能,那么创建C时只能使用shared_ptr,使用普通的new或者在栈上创建都是不行的。

下面看下enable_shared_from_this代码,其实不过是一个weak_ptr:

template<class _Ty> class enable_shared_from_this

{ // provide member functions that create shared_ptr to this

public:

typedef _Ty _EStype;

shared_ptr<_Ty> shared_from_this()

{ // return shared_ptr

return (shared_ptr<_Ty>(_Wptr));

}

private:

mutable weak_ptr<_Ty> _Wptr;

};

当你写下shared_ptr<CC> c(new CC);时,会发生的事情前文都已经交代,唯独有一个函数:构造shared_ptr的时候在_Ptr_base中有个函数:

template<class _Ux>

void _Resetp0(_Ux *_Px, _Ref_count_base *_Rx)

{ // release resource and take ownership of _Px

this->_Reset0(_Px, _Rx);

_Enable_shared(_Px, _Rx);

}

这里_Enable_shared前面说是空实现。但是针对派生自enable_shared_from_this的类就不是空实现了:

template<class _Ty>

inline void _Enable_shared(_Ty *_Ptr, _Ref_count_base *_Refptr,

typename _Ty::_EStype * = 0)

{ // reset internal weak pointer

if (_Ptr)

_Do_enable(_Ptr,(enable_shared_from_this<typename _Ty::_EStype>*)_Ptr, _Refptr);

}

template<class _Ty1,class _Ty2>

inline void _Do_enable(_Ty1 *_Ptr,enable_shared_from_this<_Ty2> *_Es,

_Ref_count_base *_Refptr)

{ // reset internal weak pointer

_Es->_Wptr._Resetw(_Ptr, _Refptr);

}

可以看到,在shared_ptr构造的时候,针对派生自enable_shared_from_this的类,会把类中的_Wptr这个成员变量赋值。这个操作至关重要,是enable_shared_from_this能够发挥作用的关键。

那为什么不能在构造函数中使用shared_from_this呢?这个原因很简单,因为_Wptr这个成员变量赋值是在shared_ptr的构造函数中,而shared_ptr的构造函数是在类CC构造函数之后执行的。

线程安全性

这里有一篇好文,是介绍boost::shared_ptr的线程安全性的:《为什么多线程读写 shared_ptr 要加锁?》http://blog.csdn.net/solstice/article/details/8547547

从数据结构上来看,shared_ptr以及weak_ptr的数据结构设计的非常简洁。只有_Ref_count_base这个成员是new出来的,它是可能被多个shared_ptr/weak_ptr实例共享的。

那么如果有两个处于不同线程的shared_ptr同时读写某一个_Ref_count_base时,如何保证线程安全呢?shared_ptr使用stl中普遍使用swap方法来将这个问题尽量减到最低。所以在shared_ptr的实现中,你会发现很多函数都是这样实现的:

template<class _Ty2>

_Myt& operator=(const shared_ptr<_Ty2>& _Right)

{ // assign shared ownership of resource owned by _Right

shared_ptr(_Right).swap(*this);

return (*this);

}

再看_Ref_count_base,里面有两个引用计数的成员。vs2010的实现中,使用了interlock的函数对这两个引用计数成员进行操作。

#define _MT_INCR(mtx, x) _InterlockedIncrement(&x)

#define _MT_DECR(mtx, x) _InterlockedDecrement(&x)

#define _MT_CMPX(x, y, z) _InterlockedCompareExchange(&x, y, z)

例如:

void _Incref()

{ // increment use count

_MT_INCR(_Mtx, _Uses);

}

void _Incwref()

{ // increment weak reference count

_MT_INCR(_Mtx, _Weaks);

}

void _Decref()

{ // decrement use count

if (_MT_DECR(_Mtx, _Uses) == 0)

{ // destroy managed resource, decrement weak reference count

_Destroy();

_Decwref();

}

}

void _Decwref()

{ // decrement weak reference count

if (_MT_DECR(_Mtx, _Weaks) == 0)

_Delete_this();

}

这样的做法使得_Ref_count_base本身是线程安全的,但是并不能完全保证能够做到多线程同时读写同一个shared_ptr对象。所以我认为这里使用interlock的函数没什么必要。

异常安全性

首先回顾下C++异常安全性的三个级别:

l 异常安全的基本保证:如果有异常抛出,资源不会泄漏。对象保持可摧毁、可使用但是不一定可预测的状态。它使用的场合是:失败的操作已经对对象状态做了改变,但是调用者代码依然能够应付。

l 异常安全的强烈保证:如果有异常抛出,程序状态保持不变。这一级别总是蕴含这”提交或者回退”的语义。例如:如果操作失败指向容器内部的引用或者迭代器不会失效

l 不抛出异常保证:在任何情况下,函数都不会产生异常。这一点对于编写底层库函数来说尤其重要。

在std::tr1::shared_ptr的实现中,绝大多数函数中都恪守不抛出异常的原则。只有3个函数直接抛出异常的地方,不过也要看是否定义了HAS_EXCEPTIONS宏。这3个函数其实是同一个函数的3种重载,对应3种shared_ptr的构造方式:

template<class _Ux>

void _Resetp(_Ux *_Px)

{ // release, take ownership of _Px

_TRY_BEGIN // allocate control block and reset

_Resetp0(_Px, new _Ref_count<_Ux>(_Px));

_CATCH_ALL // allocation failed, delete resource

delete _Px;

_RERAISE;

_CATCH_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

}

//#if _HAS_CPP0X

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

}

意图其实也很简单:如果调用_ Resetp0的过程中有异常抛出,把new出来的对象删除掉,然后把异常传递出去。根据_Resetp0函数代码分析,_Resetp0执行过程中最有可能抛出异常的情况有:

l 内存不足,bad alloc异常被抛出

l 在调用_Ref_count_base::_Decref的过程中会调用_Destroy(),这是个纯虚函数,可能被实现为抛出异常。

l 用户实现的析构函数可能会抛出异常。

但是,仔细看这几个try包住的地方,全部都是new一个全新的_Ref_count_base,也就是说可能抛出异常的情况2和情况3不可能出现。只可能出现内存不足抛出异常的情况。所以我认为这里的异常处理画蛇添足。


————————以上摘自:std::tr1::shared_ptr源码赏析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值