智能指針

指針非常強大,是c++的精髓所在,但用裸指針總有點心驚肉跳,怕一個不小心就引起內存問題,排查起來就相當費時費力了。裸指針有哪些問題:

  • 忘記釋放資源,導致資源泄露(常發生內存泄漏問題)
  • 同一資源釋放多次,導致釋放野指針,程序崩潰
  • 已寫了釋放資源的代碼,但是由于程序邏輯不滿足條件,導致釋放- 資源的代碼未被執行到
  • 代碼運行過程中發生異常,導致釋放資源的代碼未被執行到
    智能指針就是用來解決這些問題的,它能讓開發者不關心資源的釋放,因為智能指針可以自動完成資源的釋放,它能保證無論代碼怎么跑,資源最終都會釋放

智能指針本質上是一個泛型類,類中包含傳入的指針,當開發者初始化一個智能指針時,此時智能指針是在棧上初始化,如果智能指針一旦出了作用域,它就會被回收,執行智能指針的析構函數,智能指針則可趁機決定是否釋放內部的指針資源。

智能指針的基本原理,就是“棧上的對象出作用域會自動析構”,排出蘿卜帶出泥,自己被析構了,順便把真正的指針給釋放了

一、自定義智能指針

如果讓我們自己來實現一個智能指針,我們該怎么實現呢?

  • 智能回收,如果智能指針被回收,需要判斷,真正的指針是否要被回收
  • 重定義操作符,*號以及 → 等,智能指針和裸指針的使用體驗相同
  • 指針計數,如果有多個指針指向同一個對象,應該通過計數解決指針,因為一個智能指針到生命周期了,但此對象還被其它智能指針引用著,所以還不能回收對象

按照這幾個要求,我們寫一個相當簡單的智能指針

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;
}
}

執行對應測試方法,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

上面的代碼已經初步實現一個智能指針,當兩個智能指針指向同一個對象時,ptr收回時,因為count值為1,說明外邊還有一個智能指針在引用此對象,因此不能回收,等到ptr2回收時,count為0了,才回收相應對象。但上面的示例代碼還是比較簡單,因為沒有考慮多線程情況,在源碼中是通過cas操作來實現線程安全的。

另外此處還有一個小細節,m_count為什么是一個指針?如果m_count只是一個int值,那么在執行復制構造函數時,只能更改自身的m_count值,其它智能指針的m_count值無法更改或者改得比較麻煩。因為用其它智能指針賦值生成一個新的智能指針時,新舊兩個智能指針的m_count值都應該加1,所以,用指針就方便多了,新舊兩個智能指針的m_count指向同一塊內存區域,這樣,改了一處,另一處也就更改了。

比對自定義智能指針的相關代碼,我們先來看看shared_ptr和weak_ptr的用法

二、智能指針的用法

shared_ptr和weak_ptr,都是帶引用計數的智能指針。同之前的自定義智能指針一樣,當允許多個智能指針指向同一個資源的時候,每一個智能指針都會給資源的引用計數加1,當一個智能指針析構時,同樣會使資源的引用計數減1,這樣最后一個智能指針把資源的引用計數從1減到0時,就說明該資源可以釋放了。

shared_ptr,強智能指針,可以多個shared_ptr指向同一個資源,也是使用得最普遍的智能指針,但它有個問題,它不能解決循環引用問題。

  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

循環引用下,出main函數作用域,ptra和ptrb兩個局部對象析構,分別給A對象和 B對象的引用計數從2減到1,達不到釋放A和B的條件(釋放的條件是 A和B的引用計數為0),因此造成兩個new出來的A和B對象無法釋放, 導致內存泄露,這個問題就是“強智能指針的交叉引用(循環引用)問題”。weak_ptr則可以解決這種問題,將A 和 B 類中的智能指針改為weak_ptr,即可解決上述問題

弱智能指針weak_ptr區別于shared_ptr之處在于:

  • weak_ptr不會改變資源的引用計數,只是一個觀察者的角色,通過觀察shared_ptr來判定資源是否存在
  • weak_ptr持有的引用計數,不是資源的引用計數,而是同一個資源的觀察者的計數
  • weak_ptr沒有提供常用的指針操作,無法直接訪問資源,需要先通過lock方法提升為shared_ptr強智能指針,才能訪問資源

一般來說,使用智能指針可以使用以下原則:定義對象時,用強智能指針shared_ptr,在其它地方引用對象時,使用弱智能指針weak_ptr。

三、源碼分析

這里分析的源碼來自于gcc-10.2.0,gcc-10.2.0/libstdc++-v3/include/tr1/shared_ptr.h
shared_ptr和weak_ptr牽涉有好幾個不同的類,先來看看它們牽涉哪些類以及相應的關系:

shared_ptr繼承__shared_ptr,而__shared_ptr中有兩個成員變量:

  • _Tp*,真正的指針,指向要操作的數據
  • __shared_count,用于計數相關的邏輯

weak_ptr也是同樣的結構。雙方的count成員變量都引用著一個_Sp_counted_base指針,所以,先來看看_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; // 當use count為0時,釋放真實指針
  
 // Called when _M_weak_count drops to zero.
 virtual void
 _M_destroy() // 當weak count為0時,銷毀自己
 { 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時需要調用的方法
  
 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數
 {
   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,代表著有多少個shared_ptr指向了引用數據,而_M_weak_count則代表了weak_ptr的個數。

當_M_release方法時,如果_M_use_count等于1,自減之后等于0,則表示沒有shared_ptr再指向相應資源了,則要回收掉相應的資源,即那個管理的真實的指針。如果_M_weak_count自減之后等于0,則需要調用_M_destroy方法,銷毀自己。

virtual void
_M_dispose() // nothrow
{ _M_del(_M_ptr); }

_M_dispose方法的實現在_Sp_counted_base_impl 中,刪除對應指針

接下來一起看看__shared_count類的源碼(有刪減,將一些重點突出)

template<_Lock_policy _Lp = __default_lock_policy>
class __shared_count
{
public:
  __shared_count()
  : _M_pi(0) // nothrow
  { }
//析構函數,執行_Sp_counted_base的release方法,use_count自減,判斷是否要刪除管理的指針
//weak_count自減,判斷是否要刪除 _Sp_counted_base 自身的指針,而 _Sp_counted_base 也是以指針形式保存在 __shared_count中
  ~__shared_count() // nothrow
  {
if (_M_pi != 0)
  _M_pi->_M_release();
  }
  //復制構造函數,執行_M_add_ref_copy方法,自己以及被復制的對象,use_count都會自增一
  __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;
};

從源碼中可以看出,當執行復制構造函數時,會執行_Sp_counted_base的_M_add_ref_copy方法,use_count將會自增。當執行析構函數時,將會執行_Sp_counted_base的release方法,而release方法中將會檢查use_count和weak_count,刪除管理的指針或_Sp_counted_base自身。_Sp_counted_base正好也是以指針形式存在于__shared_count中,執行_Sp_counted_base的destroy方法時,_Sp_counted_base的裸指針將被刪除,不會有泄漏。

整個思路和前文第一部分的自定義智能指針一模一樣

接下來我們再看看__weak_count的源碼

template<_Lock_policy _Lp>
class __weak_count
{
public:
  __weak_count()
  : _M_pi(0) // nothrow
  { }
//復制構建函數,只是調用_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();
  }
  //復制構建函數,只是調用_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不同的是,執行復制構造函數時,只是自增weak_count的值。執行析構函數時,執行_Sp_counted_base的_M_weak_release方法,_M_weak_release方法會判斷weak_count數量,決定是否釋放_Sp_counted_base的指針,__weak_count的析構函數并不會釋放管理的真正的指針。

接下來看看__shared_ptr類

 //使用__weak_count作參數的復制構造函數,意味著此智能指針要轉化為shared_ptr,不再是weak_ptr
//所以,需要調用_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;
//默認構造函數
__shared_ptr()
: _M_ptr(0), _M_refcount() // never throws
{ }

//使用__shared_ptr作為參數的復制構造函數
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作為參數的復制構造函數,__shared_ptr的成員變量_M_refcount是__shared_count
//而__r._M_refcount是__weak_count,__shared_count的這類復制構造函數前最前面,它將會調用_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;
}
//模擬指針使用方法而重寫的運算符函數
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的幾個復制構造函數,它可以由__shared_ptr復制,也可以由__weak_ptr構造,當由__weak_ptr構造時,執行_M_add_ref_lock方法,其實是將weak_ptr轉換成了shared_ptr,同時自增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并不能直接獲取管理的指針,需要通過調用lock方法,轉成shared_ptr,才能獲取管理的指針并且完成賦值
  //而_M_refcount,根據weak_count的源碼說明,只是調用_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轉換成一個shared_ptr,調用shared_ptr的一個復制構造函數
  __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的復制構造函數只會自增weak count,不會自增use count,所以完全不會影響管理的指針的釋放。

綜上所述,__shared_ptr擁有成員變量__shared_count,而__shared_count擁有成員變量,準確說是一個指針,_Sp_counted_base*,_Sp_counted_base內有兩個成員變量_M_use_count和_M_weak_count,當初始化__shared_ptr時,_M_use_count自增,用其它shared_ptr來初始化一個新的shared_ptr時,則二者的_M_use_count都會加1,最終在棧內,shared_ptr析構時,會計算當前_M_use_count是否為0,如果為0,則釋放管理的指針,如果_M_weak_count也為0,則將內部的成員變量指針_Sp_counted_base釋放。

__weak_ptr,和上述類似,只是它在初始化時是_M_weak_count自增,完全不影響_M_use_count,它析構時,依然會調用__weak_count的析構函數,即調用_Sp_counted_base的_M_weak_release方法,此方法只會判斷weak_count是否為0,如果是0,則刪除_Sp_counted_base指針,根本不會影響管理的真實指針。__weak_ptr通過調用lock方法可轉換成__shared_ptr,其實也就是調用__shared_ptr的復制構造函數而已,不過use count會自增

通過這么多的講解,weak_ptr為什么能解決雙循環引用的問題呢?原因還是在于weak_count的設計,不會增加use count,所以不會干擾管理的指針回收。

而shared_ptr為什么能自動回收管理的指針呢,通過棧自動回收超出作用域的對象,回收shared_ptr時,根據use count決定是否回收管理的指針。

最后,貌似講了半天,也沒提是如何刪除管理的真正的指針。_Sp_counted_base_impl繼承_Sp_counted_base,它多了兩個成員變量,_M_ptr和_M_del。其實在__shared_ptr初始化時,則會去初始化__shared_count,再去初始化_Sp_counted_base_impl,__shared_ptr內管理的指針會傳遞給_M_ptr,而_M_del是一個負責刪除指針的結構體,所以在__shared_ptr析構時,會執行_shared_count的析構,而_shared_count析構,則會執行_Sp_counted_base_impl的_M_release方法,_M_release方法中會調用_M_dispose,回收管理的指針

除了shared_ptr和weak_ptr之外,還有一個智能指針,unique_ptr,顧名思義,它就是一個原生指針獨一無二的擁有者和管理者,它不允許別的unique_ptr再占用原生指針,甚至它的復制構造函數以及賦值函數都是不允許調用的。

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));

要用它,只能通過右值賦值或者直接傳原生指針才行。

unique_ptr,它的原理就是通過右值賦值,實現一人獨占,因為它是一人獨占,所以根本不用計數了,unique_ptr自己的生命同期到了,管理的原生指針也會跟著回收了

它的用法較其它更簡單一些,在此不多做介紹,以后再講右值的時候再講

四、enable_shared_from_this分析
智能指針有一個坑存在。

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,導致stu_ptr將會被回收兩次,程序報錯。

智能指針也不智能的原因在于shared_ptr的構造方法,如果不是調用復制構造函數,而是傳入被管理的指針,那么對應的_M_refcount將會執行默認初始化方法,從前文可知,執行默認的初始化方法,那么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);
}

所以,想要讓指向同一指針的智能指針計數正常,第二個智能指針只能用復制構造函數,通過其它智能指針賦值才行。

回到 enable_shared_from_this,它的主要作用是提供一個函數,返回當前對象的一個shared_ptr。如果按照正常思路,得這么寫:

shared_ptr<stu> getSharePtr(){
return shared_ptr<stu>(this);
}

但這樣寫正好踩了前面的坑,導致use_count為1,這個對象會被回收兩次,肯定是不行的。從前面可知,如果要返回一個正常的智能指針,必須用其它智能指針來賦值。enable_shared_from_this就是用來解決這個問題的。

回看前面__shared_ptr的構建函數,它還調用了__enable_shared_from_this_helper方法,這個方法是干啥的呢?

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);
}

如果某個對象繼承__enable_shared_from_this,在構建__shared_ptr時,傳入自身類型的指針,其實也可以看作是傳入了__enable_shared_from_this指針,因為繼承自__enable_shared_from_this,可以轉換成這種指針,然后調用_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中有個弱智能指針成員變量,_M_weak_this,調用__weak_ptr的_M_assign方法,其實就是初始化__weak_ptr兩個成員變量,生成一個非空的__weak_ptr。

繼承__enable_shared_from_this的對象,想要獲取指向自身的__shared_ptr,調用shared_from_this方法即可,將一個弱智能指針_M_weak_this轉換成一個強智能指針,目的就實現了。

五、shared_ptr的線程安全

智能指針的線程安全問題,與一個樸素的流程問題相關:把大象關冰箱分成幾步,三步

那么,生成一個shared_ptr分成幾步,兩步:

  • 引用計數
  • 指針賦值

引用計數是線程安全的,毋庸置疑,因為引用計數采用了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();
          }
  }
  }

鎖可以分成樂觀鎖和悲觀鎖,悲觀鎖即是一般意義上的加鎖,互斥同步,不管干什么,先加鎖保護起來,然后操作。但加鎖會導致代碼運行效率變低,因為涉及到線程切換等各種事情。悲觀鎖就是使用互斥同步的手段來保證線程安全的

樂觀鎖,和悲觀鎖相反,不用互斥同步,但它依賴于硬件,因為我們需要操作和沖突檢測這兩個步驟具備原子性,如果不考慮互斥來實現,那只能使用硬件來完成了,硬件保證一個從主觀上看起來需要多次操作的行為只通過一條處理器指令就能完成。

cas就是一種樂觀鎖,通過原子操作來實現多線程安全地寫數據

指針賦值是線程安全的嗎?明顯不是的,源碼中沒有看到任何一處與指針賦值有關的線程安全代碼。所以,shared_ptr賦值操作有兩個步驟,但有一個步驟是不安全的,那么shared_ptr就是不安全的了。哪些操作是不安全的呢?可以參考 https://www.boost.org/doc/libs/1_73_0/libs/smart_ptr/doc/html/smart_ptr.html#shared_ptr_thread_safety 的幾個示例,如果是多個線程讀shared_ptr,那肯定是安全的,但如果是多個線程寫 shared_ptr,那就不安全了。

關于線程不安全的問題,用圖來說明就會更好理解了:

所以,遇到這種多線程寫指針的情況,還是老老實實地加鎖干活吧。值得一提的是,某些特殊的場景,可以靈活使用weak_ptr來做探測shared_ptr是否已經被回收了,不用加鎖而解決部分的多線程問題。

因為weak_ptr的lock方法是通過檢測 use_count值來判斷shared_ptr是否已經被回收,如果沒有被回收,則生成正確的shared_ptr,如果已回收,則生成一個空的shared_ptr,所以可以靈活使用weak_ptr,它可以有效地探測shared_ptr是否還存在,從而解決部分多線程問題。

__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();
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容

  • 導語: C++指針的內存管理相信是大部分C++入門程序員的夢魘,受到Boost的啟發,C++11標準推出了智能指針...
    7ee72f98ad17閱讀 906評論 0 1
  • 學c++的人都知道,在c++里面有一個痛點,就是動態內存的管理,就我所經歷的一些問題來看,很多莫名其妙的問題,最后...
    cpp加油站閱讀 825評論 0 2
  • 12章之前的程序中使用的對象都有嚴格定義的生存期。 全局對象在程序啟動時分配,在程序結束時銷毀。 對于局部自動對象...
    Kreat閱讀 444評論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,577評論 28 53
  • 首先介紹下自己的背景: 我11年左右入市到現在,也差不多有4年時間,看過一些關于股票投資的書籍,對于巴菲特等股神的...
    瞎投資閱讀 5,753評論 3 8