enable_shared_from_this類的作用和實現


使用舉例

有時候我們需要在一個被 shared_ptr 管理的對象的內部獲取自己的 shared_ptr, 比如下面這個簡單的例子:

  • 通過 this 指針來構造一個 shared_ptr:
 1 struct Bad
 2 {
 3     void fun()
 4     {
 5         shared_ptr<Bad> sp{this};
 6         cout<<sp->count()<<endl;
 7     }
 8 };
 9 shared_ptr<Bad> sp{make_shared<Bad>()};
10 sp->fun(); //輸出為1

但是注意, 在 func 函數構造智能指針時, 我們無法確定這個對象是不是被 shared_ptr 管理着, 因此這樣構造的 shared_ptr 並不是與其他 shared_ptr 共享一個計數器, 那么, 在析構時就會導致對象被重復釋放, 從而引發錯誤.

現在明確一下我們的需求: 在一個對象內部構造該對象的 shared_ptr 時, 即使該對象已經被 shared_ptr 管理着, 也不會造成對象被兩個獨立的智能指針管理.

這就要求我們在對象內構造對象的智能指針時, 必須能識別有對象是否已經由其他智能指針管理, 智能指針的數量, 並且我們創建智能指針后也能讓之前的智能指針感知到.

正確做法是繼承 enable_shared_from_this 類, 調用 shared_from_this() 函數生成 shared_ptr, 使用如下:

 1 struct Good : public std::enable_shared_from_this<Good>
 2 {
 3   void fun()
 4   {
 5     shared_ptr<Good> sp{shared_from_this()};
 6     cout<<sp->count()<<endl;
 7   }
 8 };
 9 shared_ptr<Good> sp{make_shared<Good>()};//--------------*1*
10 sp->fun(); //輸出為2

在類內部通過 enable_shared_from_this 定義的 shared_from_this() 函數構造一個 shared_ptr<Good> 對象, 能和其他 shared_ptr 共享 Good 對象.

enable_shared_from_this的實現分析(基於gcc-7.2.0的源碼)

gcc是通過 weak_ptr 來實現的. 先用要管理對象(obj)的指針和已有的管理obj的 shared_ptr(sp1,...,spn) 的個數(spi->use_count())來初始化一個 weak_ptr<Obj>(&obj , spi->use_count()), 然后用這個 weak_ptr 構造一個 shared_ptr.

 1 // enable_shared_from_this的實現
 2 // 基於(/usr/include/c++/7.3.0/bits/shared_ptr.h)
 3 // 此代碼是對gcc實現的簡化版本, 僅作為描述原理用.    
 4 template<typename T>
 5 class enable_shared_from_this
 6 {
 7 public:
 8     shared_ptr<T> shared_from_this()
 9     {
10         return shared_ptr<T>(this->weak_this);
11     }
12     shared_ptr<const T> shared_from_this() const
13     {
14         return shared_ptr<const T>(this->weak_this);
15     }
16 private:
17     template<typename>
18     friend class shared_ptr;
19 
20     template<typename T1>
21     void _M_weak_assign(T1* p, const shared_count<>& n)
22     {
23       weak_this._M_assign(p, n);
24     }
25 
26     mutable weak_ptr<T> weak_this;
27 };

enable_shared_from_this<T> 類中定義了一個 weak_ptr<T>, 起到了上文提到的從obj指針生成 shared_ptr<T> 對象的作用. 按照先前的原理, 我們可能認為是在obj初始化的時候, 同時對 weak_this 進行初始化, 但是在這段代碼里顯然沒有對 weak_this 進行任何初始化工作(原始代碼里也沒有, gcc為什么不這樣實現呢? 這是因為當對象沒有由智能指針管理時, 這些操作是沒有必要的. 所以應該把這個任務交給 shared_ptr).

gcc在 shared_ptr<T> 的構造函數中對 weak_ptr<T> 進行處理. 從 Good 類來看, 就是在 *1* 處對 Good 對象中的 weak_ptr<Good> weak_this 進行處理, 使其指向一個有效的 Good 對象, 並修改 use_count. 上面 Good 類對 enable_shared_from_this 的使用是少數幾種有效的方法, 必須保證, 如果對一個對象調用 shared_from_this(), 該對象必須是由 shared_ptr<T> 持有的. 從上一段的原理中可以理解這樣做的原因: 第一個持有 Good 對象 g_objshared_ptr<T> sp1 會對 g_objweak_this 進行處理, 使其有效. 如果沒有這一步, 在調用 shared_from_this() 時, weak_this 是一個無效值, 即 weak_this.expire() == true, 就會拋出異常.

那么在 shared_ptr 的構造函數中是如何處理 weak_ptr 的呢?

shared_ptr 中定義了這樣一個函數(來自/usr/include/c++/7.3.0/bits/shared_ptr_base.h中類__shared_ptr):

 1 template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
 2 typename enable_if<__has_esft_base<_Yp2>::value>::type
 3 _M_enable_shared_from_this_with(_Yp* __p) noexcept
 4 {
 5     if(auto __base = __enable_shared_from_this_base(_M_refcount, __p))
 6         __base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
 7 }
 8 
 9 template<typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
10 typename enable_if<!__has_esft_base<_Yp2>::value>::type
11 _M_enable_shared_from_this_with(_Yp*) noexcept { }

其中 _Ypshared_ptr 管理的對象的類型. 這兩個模板函數表示:

_Yp enable_shared_from_this 的子類時, 就會生成第一個函數, 其功能是通過 _Yp 對象的指針來調用其 _M_weak_assign 函數以修改 _Yp 對象的 weak_this 成員, 而實際上 _M_weak_assign 調用的是 _M_assign 函數. 

否則生成第二個函數體為空的函數.

 1 // from shared_ptr_base.h class __weak_ptr, derived by weak_ptr
 2 
 3 void _M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount) noexcept
 4 {
 5     if (use_count() == 0)
 6     {
 7         _M_ptr = __ptr;
 8         _M_refcount = __refcount;
 9     }
10 }

_M_enable_shared_from_this_with 函數在 shared_ptr<_Yp> 的構造函數中被調用, 從而檢測 _Yp 是否繼承自 make_shared_from_this, 並進行相應的處理. 這里的 _M_refcountshared_ptr 的成員, 用來記錄 _Yp 被多少 shared_ptr 管理. 這樣, 就完成了對 weak_ptr 的處理, 使其成為一個有效值. 在以后調用 shared_from_this() 函數時, 就能利用 weak_this 調用 shared_ptr 的構造函數, 從而生成一個共享同一對象的 shared_ptr.

上文部分代碼參考了cppreference上的實例代碼和gcc的源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM