智能指針

指針?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();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 導(dǎo)語(yǔ): C++指針的內(nèi)存管理相信是大部分C++入門(mén)程序員的夢(mèng)魘,受到Boost的啟發(fā),C++11標(biāo)準(zhǔn)推出了智能指針...
    7ee72f98ad17閱讀 1,028評(píng)論 0 1
  • 學(xué)c++的人都知道,在c++里面有一個(gè)痛點(diǎn),就是動(dòng)態(tài)內(nèi)存的管理,就我所經(jīng)歷的一些問(wèn)題來(lái)看,很多莫名其妙的問(wèn)題,最后...
    cpp加油站閱讀 892評(píng)論 0 2
  • 12章之前的程序中使用的對(duì)象都有嚴(yán)格定義的生存期。 全局對(duì)象在程序啟動(dòng)時(shí)分配,在程序結(jié)束時(shí)銷(xiāo)毀。 對(duì)于局部自動(dòng)對(duì)象...
    Kreat閱讀 597評(píng)論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂(lè)有人憂(yōu)愁,有人驚喜有人失落,有的覺(jué)得收獲滿(mǎn)滿(mǎn)有...
    陌忘宇閱讀 8,814評(píng)論 28 54
  • 首先介紹下自己的背景: 我11年左右入市到現(xiàn)在,也差不多有4年時(shí)間,看過(guò)一些關(guān)于股票投資的書(shū)籍,對(duì)于巴菲特等股神的...
    瞎投資閱讀 5,922評(píng)論 3 8

友情鏈接更多精彩內(nèi)容