目錄
在前面一文中,分析了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中主要包含以下信息:
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。