指針?lè)浅?qiáng)大,是c++的精髓所在,但用裸指針總有點(diǎn)心驚肉跳,怕一個(gè)不小心就引起內(nèi)存問(wèn)題,排查起來(lái)就相當(dāng)費(fèi)時(shí)費(fèi)力了。裸指針有哪些問(wèn)題:
- 忘記釋放資源,導(dǎo)致資源泄露(常發(fā)生內(nèi)存泄漏問(wèn)題)
- 同一資源釋放多次,導(dǎo)致釋放野指針,程序崩潰
- 已寫(xiě)了釋放資源的代碼,但是由于程序邏輯不滿(mǎn)足條件,導(dǎo)致釋放- 資源的代碼未被執(zhí)行到
- 代碼運(yùn)行過(guò)程中發(fā)生異常,導(dǎo)致釋放資源的代碼未被執(zhí)行到
智能指針就是用來(lái)解決這些問(wèn)題的,它能讓開(kāi)發(fā)者不關(guān)心資源的釋放,因?yàn)橹悄苤羔樋梢宰詣?dòng)完成資源的釋放,它能保證無(wú)論代碼怎么跑,資源最終都會(huì)釋放
智能指針本質(zhì)上是一個(gè)泛型類(lèi),類(lèi)中包含傳入的指針,當(dāng)開(kāi)發(fā)者初始化一個(gè)智能指針時(shí),此時(shí)智能指針是在棧上初始化,如果智能指針一旦出了作用域,它就會(huì)被回收,執(zhí)行智能指針的析構(gòu)函數(shù),智能指針則可趁機(jī)決定是否釋放內(nèi)部的指針資源。
智能指針的基本原理,就是“棧上的對(duì)象出作用域會(huì)自動(dòng)析構(gòu)”,排出蘿卜帶出泥,自己被析構(gòu)了,順便把真正的指針給釋放了
一、自定義智能指針
如果讓我們自己來(lái)實(shí)現(xiàn)一個(gè)智能指針,我們?cè)撛趺磳?shí)現(xiàn)呢?
- 智能回收,如果智能指針被回收,需要判斷,真正的指針是否要被回收
- 重定義操作符,*號(hào)以及 → 等,智能指針和裸指針的使用體驗(yàn)相同
- 指針計(jì)數(shù),如果有多個(gè)指針指向同一個(gè)對(duì)象,應(yīng)該通過(guò)計(jì)數(shù)解決指針,因?yàn)橐粋€(gè)智能指針到生命周期了,但此對(duì)象還被其它智能指針引用著,所以還不能回收對(duì)象
按照這幾個(gè)要求,我們寫(xiě)一個(gè)相當(dāng)簡(jiǎn)單的智能指針
template<typename T>
class smart_ptr{
private:
int* m_count;
T* m_ptr;
public:
smart_ptr():m_ptr(nullptr), m_count(nullptr){};
smart_ptr(T* ptr):m_ptr(ptr){
m_count = new int(1);
};
~smart_ptr() {
(*m_count)--;
cout << "smart ptr delete count = " << *m_count << endl;
if ((*m_count) == 0) {
delete m_ptr;
delete m_count;
}
};
smart_ptr(smart_ptr& ptr): m_ptr(ptr.m_ptr), m_count(ptr.m_count) {
(*m_count)++;
}
smart_ptr& operator=(smart_ptr& ptr){
m_ptr = ptr.m_ptr;
m_count = ptr.m_count;
(*m_count)++;
return *this;
}
int getCount(){return (*m_count);};
T& operator*(){
return *m_ptr;
}
T* operator->(){
return m_ptr;
}
};
void test_smartptr(){
{
smart_ptr<stu> ptr(new stu);
smart_ptr<stu> ptr2(ptr);
smart_ptr<stu> ptr3;
ptr3 = ptr2;
ptr->name_ptr = "tom";
cout << ptr->name_ptr << " count = " << ptr.getCount() << " ptr.count = " << ptr.getCount() << " ptr3.count = " << ptr3.getCount() << endl;
}
}
執(zhí)行對(duì)應(yīng)測(cè)試方法,log如下:
tom count = 3 ptr.count = 3 ptr3.count = 3
smart ptr delete count = 2
smart ptr delete count = 1
smart ptr delete count = 0
delete stu
上面的代碼已經(jīng)初步實(shí)現(xiàn)一個(gè)智能指針,當(dāng)兩個(gè)智能指針指向同一個(gè)對(duì)象時(shí),ptr收回時(shí),因?yàn)閏ount值為1,說(shuō)明外邊還有一個(gè)智能指針在引用此對(duì)象,因此不能回收,等到ptr2回收時(shí),count為0了,才回收相應(yīng)對(duì)象。但上面的示例代碼還是比較簡(jiǎn)單,因?yàn)闆](méi)有考慮多線(xiàn)程情況,在源碼中是通過(guò)cas操作來(lái)實(shí)現(xiàn)線(xiàn)程安全的。
另外此處還有一個(gè)小細(xì)節(jié),m_count為什么是一個(gè)指針?如果m_count只是一個(gè)int值,那么在執(zhí)行復(fù)制構(gòu)造函數(shù)時(shí),只能更改自身的m_count值,其它智能指針的m_count值無(wú)法更改或者改得比較麻煩。因?yàn)橛闷渌悄苤羔樫x值生成一個(gè)新的智能指針時(shí),新舊兩個(gè)智能指針的m_count值都應(yīng)該加1,所以,用指針就方便多了,新舊兩個(gè)智能指針的m_count指向同一塊內(nèi)存區(qū)域,這樣,改了一處,另一處也就更改了。
比對(duì)自定義智能指針的相關(guān)代碼,我們先來(lái)看看shared_ptr和weak_ptr的用法
二、智能指針的用法
shared_ptr和weak_ptr,都是帶引用計(jì)數(shù)的智能指針。同之前的自定義智能指針一樣,當(dāng)允許多個(gè)智能指針指向同一個(gè)資源的時(shí)候,每一個(gè)智能指針都會(huì)給資源的引用計(jì)數(shù)加1,當(dāng)一個(gè)智能指針析構(gòu)時(shí),同樣會(huì)使資源的引用計(jì)數(shù)減1,這樣最后一個(gè)智能指針把資源的引用計(jì)數(shù)從1減到0時(shí),就說(shuō)明該資源可以釋放了。
shared_ptr,強(qiáng)智能指針,可以多個(gè)shared_ptr指向同一個(gè)資源,也是使用得最普遍的智能指針,但它有個(gè)問(wèn)題,它不能解決循環(huán)引用問(wèn)題。
class B;
class A{
public:
A(){cout << "create a" << endl;}
~A(){cout << "destroy a" << endl;}
shared_ptr<B> _ptrb;
};
class B{
public:
B(){cout << "create b" << endl;}
~B(){cout << "destroy a" << endl;}
shared_ptr<A> _ptra;
};
void test_loop_refrence(){
shared_ptr<A> ptra(new A);
shared_ptr<B> ptrb(new B);
ptra->_ptrb = ptrb;
ptrb->_ptra = ptra;
cout << ptra.use_count() << endl;
cout << ptrb.use_count() << endl;
}
日志輸出:
create a
create a
2
2
循環(huán)引用下,出main函數(shù)作用域,ptra和ptrb兩個(gè)局部對(duì)象析構(gòu),分別給A對(duì)象和 B對(duì)象的引用計(jì)數(shù)從2減到1,達(dá)不到釋放A和B的條件(釋放的條件是 A和B的引用計(jì)數(shù)為0),因此造成兩個(gè)new出來(lái)的A和B對(duì)象無(wú)法釋放, 導(dǎo)致內(nèi)存泄露,這個(gè)問(wèn)題就是“強(qiáng)智能指針的交叉引用(循環(huán)引用)問(wèn)題”。weak_ptr則可以解決這種問(wèn)題,將A 和 B 類(lèi)中的智能指針改為weak_ptr,即可解決上述問(wèn)題
弱智能指針weak_ptr區(qū)別于shared_ptr之處在于:
- weak_ptr不會(huì)改變資源的引用計(jì)數(shù),只是一個(gè)觀(guān)察者的角色,通過(guò)觀(guān)察shared_ptr來(lái)判定資源是否存在
- weak_ptr持有的引用計(jì)數(shù),不是資源的引用計(jì)數(shù),而是同一個(gè)資源的觀(guān)察者的計(jì)數(shù)
- weak_ptr沒(méi)有提供常用的指針操作,無(wú)法直接訪(fǎng)問(wèn)資源,需要先通過(guò)lock方法提升為shared_ptr強(qiáng)智能指針,才能訪(fǎng)問(wèn)資源
一般來(lái)說(shuō),使用智能指針可以使用以下原則:定義對(duì)象時(shí),用強(qiáng)智能指針shared_ptr,在其它地方引用對(duì)象時(shí),使用弱智能指針weak_ptr。
三、源碼分析
這里分析的源碼來(lái)自于gcc-10.2.0,gcc-10.2.0/libstdc++-v3/include/tr1/shared_ptr.h
shared_ptr和weak_ptr牽涉有好幾個(gè)不同的類(lèi),先來(lái)看看它們牽涉哪些類(lèi)以及相應(yīng)的關(guān)系:

shared_ptr繼承__shared_ptr,而__shared_ptr中有兩個(gè)成員變量:
- _Tp*,真正的指針,指向要操作的數(shù)據(jù)
- __shared_count,用于計(jì)數(shù)相關(guān)的邏輯
weak_ptr也是同樣的結(jié)構(gòu)。雙方的count成員變量都引用著一個(gè)_Sp_counted_base指針,所以,先來(lái)看看_Sp_counted_base
template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base
: public _Mutex_base<_Lp>
{
public:
_Sp_counted_base()
: _M_use_count(1), _M_weak_count(1) { }
virtual
~_Sp_counted_base() // nothrow
{ }
// Called when _M_use_count drops to zero, to release the resources
// managed by *this.
virtual void
_M_dispose() = 0; // 當(dāng)use count為0時(shí),釋放真實(shí)指針
// Called when _M_weak_count drops to zero.
virtual void
_M_destroy() // 當(dāng)weak count為0時(shí),銷(xiāo)毀自己
{ delete this; }
virtual void*
_M_get_deleter(const std::type_info&) = 0;
void
_M_add_ref_copy()
{ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); } //增加use count
void
_M_add_ref_lock(); //從weak_ptr變成shared_ptr時(shí)需要調(diào)用的方法
void
_M_release() // nothrow
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1){
_M_dispose();
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1){
_M_destroy();
}
}
}
void
_M_weak_add_ref() // nothrow 增加weak count
{ __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }
void
_M_weak_release() // nothrow
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
if (_Mutex_base<_Lp>::_S_need_barriers)
{
// See _M_release(),
// destroy() must observe results of dispose()
__atomic_thread_fence (__ATOMIC_ACQ_REL);
}
_M_destroy();
}
}
long
_M_get_use_count() const // nothrow 返回use count數(shù)
{
return const_cast<const volatile _Atomic_word&>(_M_use_count);
}
private:
_Sp_counted_base(_Sp_counted_base const&);
_Sp_counted_base& operator=(_Sp_counted_base const&);
_Atomic_word _M_use_count; // #shared
_Atomic_word _M_weak_count; // #weak + (#shared != 0)
};
_M_use_count,代表著有多少個(gè)shared_ptr指向了引用數(shù)據(jù),而_M_weak_count則代表了weak_ptr的個(gè)數(shù)。
當(dāng)_M_release方法時(shí),如果_M_use_count等于1,自減之后等于0,則表示沒(méi)有shared_ptr再指向相應(yīng)資源了,則要回收掉相應(yīng)的資源,即那個(gè)管理的真實(shí)的指針。如果_M_weak_count自減之后等于0,則需要調(diào)用_M_destroy方法,銷(xiāo)毀自己。
virtual void
_M_dispose() // nothrow
{ _M_del(_M_ptr); }
_M_dispose方法的實(shí)現(xiàn)在_Sp_counted_base_impl 中,刪除對(duì)應(yīng)指針
接下來(lái)一起看看__shared_count類(lèi)的源碼(有刪減,將一些重點(diǎn)突出)
template<_Lock_policy _Lp = __default_lock_policy>
class __shared_count
{
public:
__shared_count()
: _M_pi(0) // nothrow
{ }
//析構(gòu)函數(shù),執(zhí)行_Sp_counted_base的release方法,use_count自減,判斷是否要?jiǎng)h除管理的指針
//weak_count自減,判斷是否要?jiǎng)h除 _Sp_counted_base 自身的指針,而 _Sp_counted_base 也是以指針形式保存在 __shared_count中
~__shared_count() // nothrow
{
if (_M_pi != 0)
_M_pi->_M_release();
}
//復(fù)制構(gòu)造函數(shù),執(zhí)行_M_add_ref_copy方法,自己以及被復(fù)制的對(duì)象,use_count都會(huì)自增一
__shared_count(const __shared_count& __r)
: _M_pi(__r._M_pi) // nothrow
{
if (_M_pi != 0)
_M_pi->_M_add_ref_copy();
}
long
_M_get_use_count() const // nothrow
{ return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0; }
_Sp_counted_base<_Lp>* _M_pi;
};
從源碼中可以看出,當(dāng)執(zhí)行復(fù)制構(gòu)造函數(shù)時(shí),會(huì)執(zhí)行_Sp_counted_base的_M_add_ref_copy方法,use_count將會(huì)自增。當(dāng)執(zhí)行析構(gòu)函數(shù)時(shí),將會(huì)執(zhí)行_Sp_counted_base的release方法,而release方法中將會(huì)檢查use_count和weak_count,刪除管理的指針或_Sp_counted_base自身。_Sp_counted_base正好也是以指針形式存在于__shared_count中,執(zhí)行_Sp_counted_base的destroy方法時(shí),_Sp_counted_base的裸指針將被刪除,不會(huì)有泄漏。
整個(gè)思路和前文第一部分的自定義智能指針一模一樣
接下來(lái)我們?cè)倏纯確_weak_count的源碼
template<_Lock_policy _Lp>
class __weak_count
{
public:
__weak_count()
: _M_pi(0) // nothrow
{ }
//復(fù)制構(gòu)建函數(shù),只是調(diào)用_M_weak_add_ref,自增weak_count
__weak_count(const __shared_count<_Lp>& __r)
: _M_pi(__r._M_pi) // nothrow
{
if (_M_pi != 0)
_M_pi->_M_weak_add_ref();
}
//復(fù)制構(gòu)建函數(shù),只是調(diào)用_M_weak_add_ref,自增weak_count
__weak_count(const __weak_count<_Lp>& __r)
: _M_pi(__r._M_pi) // nothrow
{
if (_M_pi != 0)
_M_pi->_M_weak_add_ref();
}
~__weak_count() // nothrow
{
if (_M_pi != 0)
_M_pi->_M_weak_release();
}
long
_M_get_use_count() const // nothrow
{ return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0; }
_Sp_counted_base<_Lp>* _M_pi;
};
與__shared_count不同的是,執(zhí)行復(fù)制構(gòu)造函數(shù)時(shí),只是自增weak_count的值。執(zhí)行析構(gòu)函數(shù)時(shí),執(zhí)行_Sp_counted_base的_M_weak_release方法,_M_weak_release方法會(huì)判斷weak_count數(shù)量,決定是否釋放_(tái)Sp_counted_base的指針,__weak_count的析構(gòu)函數(shù)并不會(huì)釋放管理的真正的指針。
接下來(lái)看看__shared_ptr類(lèi)
//使用__weak_count作參數(shù)的復(fù)制構(gòu)造函數(shù),意味著此智能指針要轉(zhuǎn)化為shared_ptr,不再是weak_ptr
//所以,需要調(diào)用_M_add_ref_lock方法,自增use_count
template<_Lock_policy _Lp>
inline
__shared_count<_Lp>::
__shared_count(const __weak_count<_Lp>& __r)
: _M_pi(__r._M_pi)
{
if (_M_pi != 0)
_M_pi->_M_add_ref_lock();
else
__throw_bad_weak_ptr();
}
template<typename _Tp, _Lock_policy _Lp>
class __shared_ptr
{
public:
typedef _Tp element_type;
//默認(rèn)構(gòu)造函數(shù)
__shared_ptr()
: _M_ptr(0), _M_refcount() // never throws
{ }
//使用__shared_ptr作為參數(shù)的復(fù)制構(gòu)造函數(shù)
template<typename _Tp1>
__shared_ptr(const __shared_ptr<_Tp1, _Lp>& __r, __static_cast_tag)
: _M_ptr(static_cast<element_type*>(__r._M_ptr)),
_M_refcount(__r._M_refcount)
{ }
//使用__weak_ptr作為參數(shù)的復(fù)制構(gòu)造函數(shù),__shared_ptr的成員變量_M_refcount是__shared_count
//而__r._M_refcount是__weak_count,__shared_count的這類(lèi)復(fù)制構(gòu)造函數(shù)前最前面,它將會(huì)調(diào)用_M_add_ref_lock方法,自增use_count
template<typename _Tp1>
explicit
__shared_ptr(const __weak_ptr<_Tp1, _Lp>& __r)
: _M_refcount(__r._M_refcount) // may throw
{
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
// It is now safe to copy __r._M_ptr, as _M_refcount(__r._M_refcount)
// did not throw.
_M_ptr = __r._M_ptr;
}
//模擬指針使用方法而重寫(xiě)的運(yùn)算符函數(shù)
operator*() const // never throws
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return *_M_ptr;
}
_Tp*
operator->() const // never throws
{
_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);
return _M_ptr;
}
_Tp*
get() const // never throws
{ return _M_ptr; }
// Implicit conversion to "bool"
public:
long
use_count() const // never throws
{ return _M_refcount._M_get_use_count(); }
_Tp* _M_ptr; // Contained pointer.
__shared_count<_Lp> _M_refcount; // Reference counter.
};
注意__shared_ptr的幾個(gè)復(fù)制構(gòu)造函數(shù),它可以由__shared_ptr復(fù)制,也可以由__weak_ptr構(gòu)造,當(dāng)由__weak_ptr構(gòu)造時(shí),執(zhí)行_M_add_ref_lock方法,其實(shí)是將weak_ptr轉(zhuǎn)換成了shared_ptr,同時(shí)自增use_cont
最后,一起看看__weak_ptr的代碼
template<typename _Tp, _Lock_policy _Lp>
class __weak_ptr
{
public:
typedef _Tp element_type;
__weak_ptr()
: _M_ptr(0), _M_refcount() // never throws
{ }
//weak_ptr并不能直接獲取管理的指針,需要通過(guò)調(diào)用lock方法,轉(zhuǎn)成shared_ptr,才能獲取管理的指針并且完成賦值
//而_M_refcount,根據(jù)weak_count的源碼說(shuō)明,只是調(diào)用_M_weak_add_ref,自增weak count
template<typename _Tp1>
__weak_ptr(const __weak_ptr<_Tp1, _Lp>& __r)
: _M_refcount(__r._M_refcount) // never throws
{
_M_ptr = __r.lock().get();
}
template<typename _Tp1>
__weak_ptr(const __shared_ptr<_Tp1, _Lp>& __r)
: _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount) // never throws
{ __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>) }
//lock方法,將weak_ptr轉(zhuǎn)換成一個(gè)shared_ptr,調(diào)用shared_ptr的一個(gè)復(fù)制構(gòu)造函數(shù)
__shared_ptr<_Tp, _Lp>
lock() const // never throws
{
__try
{
return __shared_ptr<element_type, _Lp>(*this);
}
__catch(const bad_weak_ptr&)
{
return __shared_ptr<element_type, _Lp>();
}
} // XXX MT
long
use_count() const // never throws
{ return _M_refcount._M_get_use_count(); }
private:
_Tp* _M_ptr; // Contained pointer.
__weak_count<_Lp> _M_refcount; // Reference counter.
};
與__shared_ptr不同的是,__weak_ptr的復(fù)制構(gòu)造函數(shù)只會(huì)自增weak count,不會(huì)自增use count,所以完全不會(huì)影響管理的指針的釋放。
綜上所述,__shared_ptr擁有成員變量__shared_count,而__shared_count擁有成員變量,準(zhǔn)確說(shuō)是一個(gè)指針,_Sp_counted_base*,_Sp_counted_base內(nèi)有兩個(gè)成員變量_M_use_count和_M_weak_count,當(dāng)初始化__shared_ptr時(shí),_M_use_count自增,用其它shared_ptr來(lái)初始化一個(gè)新的shared_ptr時(shí),則二者的_M_use_count都會(huì)加1,最終在棧內(nèi),shared_ptr析構(gòu)時(shí),會(huì)計(jì)算當(dāng)前_M_use_count是否為0,如果為0,則釋放管理的指針,如果_M_weak_count也為0,則將內(nèi)部的成員變量指針_Sp_counted_base釋放。
__weak_ptr,和上述類(lèi)似,只是它在初始化時(shí)是_M_weak_count自增,完全不影響_M_use_count,它析構(gòu)時(shí),依然會(huì)調(diào)用__weak_count的析構(gòu)函數(shù),即調(diào)用_Sp_counted_base的_M_weak_release方法,此方法只會(huì)判斷weak_count是否為0,如果是0,則刪除_Sp_counted_base指針,根本不會(huì)影響管理的真實(shí)指針。__weak_ptr通過(guò)調(diào)用lock方法可轉(zhuǎn)換成__shared_ptr,其實(shí)也就是調(diào)用__shared_ptr的復(fù)制構(gòu)造函數(shù)而已,不過(guò)use count會(huì)自增
通過(guò)這么多的講解,weak_ptr為什么能解決雙循環(huán)引用的問(wèn)題呢?原因還是在于weak_count的設(shè)計(jì),不會(huì)增加use count,所以不會(huì)干擾管理的指針回收。
而shared_ptr為什么能自動(dòng)回收管理的指針呢,通過(guò)棧自動(dòng)回收超出作用域的對(duì)象,回收shared_ptr時(shí),根據(jù)use count決定是否回收管理的指針。
最后,貌似講了半天,也沒(méi)提是如何刪除管理的真正的指針。_Sp_counted_base_impl繼承_Sp_counted_base,它多了兩個(gè)成員變量,_M_ptr和_M_del。其實(shí)在__shared_ptr初始化時(shí),則會(huì)去初始化__shared_count,再去初始化_Sp_counted_base_impl,__shared_ptr內(nèi)管理的指針會(huì)傳遞給_M_ptr,而_M_del是一個(gè)負(fù)責(zé)刪除指針的結(jié)構(gòu)體,所以在__shared_ptr析構(gòu)時(shí),會(huì)執(zhí)行_shared_count的析構(gòu),而_shared_count析構(gòu),則會(huì)執(zhí)行_Sp_counted_base_impl的_M_release方法,_M_release方法中會(huì)調(diào)用_M_dispose,回收管理的指針
除了shared_ptr和weak_ptr之外,還有一個(gè)智能指針,unique_ptr,顧名思義,它就是一個(gè)原生指針獨(dú)一無(wú)二的擁有者和管理者,它不允許別的unique_ptr再占用原生指針,甚至它的復(fù)制構(gòu)造函數(shù)以及賦值函數(shù)都是不允許調(diào)用的。
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
unique_ptr(unique_ptr&&) = default;
unique_ptr& operator=(unique_ptr&&) = default;
用法:
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
std::unique_ptr<Task>taskPtr5(new Task(55));
要用它,只能通過(guò)右值賦值或者直接傳原生指針才行。
unique_ptr,它的原理就是通過(guò)右值賦值,實(shí)現(xiàn)一人獨(dú)占,因?yàn)樗且蝗霜?dú)占,所以根本不用計(jì)數(shù)了,unique_ptr自己的生命同期到了,管理的原生指針也會(huì)跟著回收了
它的用法較其它更簡(jiǎn)單一些,在此不多做介紹,以后再講右值的時(shí)候再講
四、enable_shared_from_this分析
智能指針有一個(gè)坑存在。
stu* stu_ptr = new stu("seven");
shared_ptr<stu> ptr1(stu_ptr);
shared_ptr<stu> ptr2(stu_ptr);
cout << " count1 = " << ptr1.use_count() << endl;
cout << " count2 = " << ptr2.use_count() << endl;
輸出的log:
count1 = 1
count2 = 1
delete stu
delete stu
明明ptr1 和 ptr2都是管理著stu_ptr,但它們的use_count方法返回值分別為1,而不是2,導(dǎo)致stu_ptr將會(huì)被回收兩次,程序報(bào)錯(cuò)。
智能指針也不智能的原因在于shared_ptr的構(gòu)造方法,如果不是調(diào)用復(fù)制構(gòu)造函數(shù),而是傳入被管理的指針,那么對(duì)應(yīng)的_M_refcount將會(huì)執(zhí)行默認(rèn)初始化方法,從前文可知,執(zhí)行默認(rèn)的初始化方法,那么use count將為1
template<typename _Tp1>
explicit
__shared_ptr(_Tp1* __p)
: _M_ptr(__p), _M_refcount(__p)
{
__glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
typedef int _IsComplete[sizeof(_Tp1)];
__enable_shared_from_this_helper(_M_refcount, __p, __p);
}
所以,想要讓指向同一指針的智能指針計(jì)數(shù)正常,第二個(gè)智能指針只能用復(fù)制構(gòu)造函數(shù),通過(guò)其它智能指針賦值才行。
回到 enable_shared_from_this,它的主要作用是提供一個(gè)函數(shù),返回當(dāng)前對(duì)象的一個(gè)shared_ptr。如果按照正常思路,得這么寫(xiě):
shared_ptr<stu> getSharePtr(){
return shared_ptr<stu>(this);
}
但這樣寫(xiě)正好踩了前面的坑,導(dǎo)致use_count為1,這個(gè)對(duì)象會(huì)被回收兩次,肯定是不行的。從前面可知,如果要返回一個(gè)正常的智能指針,必須用其它智能指針來(lái)賦值。enable_shared_from_this就是用來(lái)解決這個(gè)問(wèn)題的。
回看前面__shared_ptr的構(gòu)建函數(shù),它還調(diào)用了__enable_shared_from_this_helper方法,這個(gè)方法是干啥的呢?
template<typename _Tp1>
friend void
__enable_shared_from_this_helper(const __shared_count<_Lp>& __pn,
const __enable_shared_from_this* __pe,
const _Tp1* __px)
{
if (__pe != 0)
__pe->_M_weak_assign(const_cast<_Tp1*>(__px), __pn);
}
如果某個(gè)對(duì)象繼承__enable_shared_from_this,在構(gòu)建__shared_ptr時(shí),傳入自身類(lèi)型的指針,其實(shí)也可以看作是傳入了__enable_shared_from_this指針,因?yàn)槔^承自__enable_shared_from_this,可以轉(zhuǎn)換成這種指針,然后調(diào)用_M_weak_assign方法
template<typename _Tp1>
void
_M_weak_assign(_Tp1* __p, const __shared_count<_Lp>& __n) const
{ _M_weak_this._M_assign(__p, __n); }
mutable __weak_ptr<_Tp, _Lp> _M_weak_this;
private:
// Used by __enable_shared_from_this.
void
_M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount)
{
_M_ptr = __ptr;
_M_refcount = __refcount;
}
__shared_ptr<_Tp, _Lp>
shared_from_this()
{ return __shared_ptr<_Tp, _Lp>(this->_M_weak_this); }
__enable_shared_from_this中有個(gè)弱智能指針成員變量,_M_weak_this,調(diào)用__weak_ptr的_M_assign方法,其實(shí)就是初始化__weak_ptr兩個(gè)成員變量,生成一個(gè)非空的__weak_ptr。
繼承__enable_shared_from_this的對(duì)象,想要獲取指向自身的__shared_ptr,調(diào)用shared_from_this方法即可,將一個(gè)弱智能指針_M_weak_this轉(zhuǎn)換成一個(gè)強(qiáng)智能指針,目的就實(shí)現(xiàn)了。
五、shared_ptr的線(xiàn)程安全
智能指針的線(xiàn)程安全問(wèn)題,與一個(gè)樸素的流程問(wèn)題相關(guān):把大象關(guān)冰箱分成幾步,三步
那么,生成一個(gè)shared_ptr分成幾步,兩步:
- 引用計(jì)數(shù)
- 指針賦值
引用計(jì)數(shù)是線(xiàn)程安全的,毋庸置疑,因?yàn)橐糜?jì)數(shù)采用了cas(compare and set)的原子操作。
_Atomic_word _M_use_count;
void
_M_weak_add_ref() // nothrow
{ __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }
void
_M_release() // nothrow
{
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
_M_dispose();
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
{
_M_destroy();
}
}
}
鎖可以分成樂(lè)觀(guān)鎖和悲觀(guān)鎖,悲觀(guān)鎖即是一般意義上的加鎖,互斥同步,不管干什么,先加鎖保護(hù)起來(lái),然后操作。但加鎖會(huì)導(dǎo)致代碼運(yùn)行效率變低,因?yàn)樯婕暗骄€(xiàn)程切換等各種事情。悲觀(guān)鎖就是使用互斥同步的手段來(lái)保證線(xiàn)程安全的
樂(lè)觀(guān)鎖,和悲觀(guān)鎖相反,不用互斥同步,但它依賴(lài)于硬件,因?yàn)槲覀冃枰僮骱蜎_突檢測(cè)這兩個(gè)步驟具備原子性,如果不考慮互斥來(lái)實(shí)現(xiàn),那只能使用硬件來(lái)完成了,硬件保證一個(gè)從主觀(guān)上看起來(lái)需要多次操作的行為只通過(guò)一條處理器指令就能完成。
cas就是一種樂(lè)觀(guān)鎖,通過(guò)原子操作來(lái)實(shí)現(xiàn)多線(xiàn)程安全地寫(xiě)數(shù)據(jù)
指針賦值是線(xiàn)程安全的嗎?明顯不是的,源碼中沒(méi)有看到任何一處與指針賦值有關(guān)的線(xiàn)程安全代碼。所以,shared_ptr賦值操作有兩個(gè)步驟,但有一個(gè)步驟是不安全的,那么shared_ptr就是不安全的了。哪些操作是不安全的呢?可以參考 https://www.boost.org/doc/libs/1_73_0/libs/smart_ptr/doc/html/smart_ptr.html#shared_ptr_thread_safety 的幾個(gè)示例,如果是多個(gè)線(xiàn)程讀shared_ptr,那肯定是安全的,但如果是多個(gè)線(xiàn)程寫(xiě) shared_ptr,那就不安全了。
關(guān)于線(xiàn)程不安全的問(wèn)題,用圖來(lái)說(shuō)明就會(huì)更好理解了:


所以,遇到這種多線(xiàn)程寫(xiě)指針的情況,還是老老實(shí)實(shí)地加鎖干活吧。值得一提的是,某些特殊的場(chǎng)景,可以靈活使用weak_ptr來(lái)做探測(cè)shared_ptr是否已經(jīng)被回收了,不用加鎖而解決部分的多線(xiàn)程問(wèn)題。
因?yàn)閣eak_ptr的lock方法是通過(guò)檢測(cè) use_count值來(lái)判斷shared_ptr是否已經(jīng)被回收,如果沒(méi)有被回收,則生成正確的shared_ptr,如果已回收,則生成一個(gè)空的shared_ptr,所以可以靈活使用weak_ptr,它可以有效地探測(cè)shared_ptr是否還存在,從而解決部分多線(xiàn)程問(wèn)題。
__shared_ptr<_Tp, _Lp>
lock() const // never throws
{
return expired() ? __shared_ptr<element_type, _Lp>()
: __shared_ptr<element_type, _Lp>(*this);
} // XXX MT
bool
expired() const // never throws
{ return _M_refcount._M_get_use_count() == 0; }
示例:
class Test{
private:
int* volatile m_ptr;
public:
Test() : m_ptr(new int(20)){
cout << "create test" << endl;
}
~Test(){
delete m_ptr;
m_ptr = nullptr;
cout << "delete test" << endl;
}
void show(){
cout << *m_ptr << endl;
}
};
void threadSafe(weak_ptr<Test> pw) {
std::this_thread::sleep_for(std::chrono::seconds(2));
shared_ptr<Test> ps = pw.lock();
if(ps != nullptr) {
ps->show();
}
}
void test_safy_smartptr(){
shared_ptr<Test> p(new Test);
std::thread t1(threadSafe, weak_ptr<Test>(p));
t1.join();
// t1.detach();
}