C++源碼—shared_ptr(MSVC 2017)


1 控制塊

shared_ptr 繼承自 _Ptr_base,它包含兩個成員變量:指向目標對象的指針 _Ptr 和 引用計數基類指針 _Rep

private:
    element_type * _Ptr{nullptr}; 
    _Ref_count_base * _Rep{nullptr};

引用計數的基類是 _Ref_count_base,在 _Ref_count_base 中,實現了計數的增加和減少,以及對象的釋放。其中_Destroy() 和 _Delete_this() 都是虛函數,它派生出了三個類 _Ref_count_Ref_count_resource _Ref_count_resource_alloc,這些也被稱為控制塊

  •  _Ref_count 只包含一個指向對象的指針,直接使用 delete 進行釋放資源和自身。
  • _Ref_count_resource 使用 _Compressed_pair<_Dx, _Resource> _Mypair 存儲刪除器和對象,在 _Destroy()中通過刪除器釋放對象,_Delete_this() 無變化。
  •  _Ref_count_resource_alloc 增加了分配器,_Destroy()無變化,_Delete_this() 通過分配器釋放自身。 

 _Compressed_pair 和 pair 差不多,但是進行了一些優化,如果 _Ty1 是空類, _Compressed_pair 就會繼承這個空類,此時 sizeof(_Compressed_pair) == sizeof(_Ty2),這就是所謂的 EBO(empty base optimization;空白基類優化)

 

 

2 shared_ptr 的構造函數

shared_ptr 有多個構造函數,根據指針、刪除器、分配器的不同去生成相應的_Ref_count,_Ref_count_resource 和 _Ref_count_resource_alloc。

以僅需指針的構造函數為例,is_array 判斷是否是數組從而去調用不同的 _Setp 重載函數,在 _Setp 中生成控制塊,然后調用_Set_ptr_rep_and_enable_shared 函數將 shared_ptr 的 _Ptr 和 _Rep 指向相應的地址。

在代碼的最后還調用了_Enable_shared_from_this 函數,這個比較復雜,放到最后介紹。 

 

3 shared_ptr 的引用計數

shared_ptr 的引用計數通過 _Rep 指向的控制塊控制,相關的操作在基類 _Ref_count_base 中已經實現,主要就是當 _Uses 為 0 時,調用 _Destroy() 釋放對象;當 _Weaks 為 0 時,調用 _Delete_this() 釋放自己,其余還有引用計數的增加和減少等函數。

 

4 make_shared 與 直接創建 shared_ptr 的區別

make_shared 只需要分配一次內存,而直接創建 shared_ptr 需要分配兩次內存

shared_ptr<string> sp{new string("hello")};

比如上面我們采用直接創建的方式,那么首先需要在堆上為 hello 分配內存,其次根據上面 shared_ptr 的構造函數可知,new _Ref_count<_Ux>(_Px) 時還需要為控制塊分配一次內存。

然后我們再來看看 make_shared 源碼,它動態分配了一個 _Ref_count_obj,這也是 _Ref_count_base 的一個派生類,aligned_union_t<1, _Ty> _Storage 為 _Ty 類型的對象分配了內存空間,所以對象和控制塊都被放在了一塊。

剩下的操作是一樣的,調用_Set_ptr_rep_and_enable_shared 函數將 shared_ptr 的 _Ptr 和 _Rep 指向相應的地址。

借用網上的一張圖,它們的區別大概就是:

當然,make_shared 也存在缺陷,上面我們提到,在_Ref_count_base的實現中,只有當 _Weaks 為 0 時,控制塊才會調用 _Delete_this() 釋放自己,然而由於現在對象也被放在了控制塊中,所以即使 _Uses 為 0 時,對象空間也沒有算被釋放,只有當 _Weaks 為 0 時, 才算是真的被釋放了

 

 

5 enable_shared_from_this

為什么需要 enable_shared_from_this,是因為引用計數和對象是分離的,在某些情況下會造成錯誤。舉一個網上常見的例子:

#include <iostream>
#include <memory>
using namespace std;

class A
{
public:
	shared_ptr<A> getPtr() {
		return shared_ptr<A>(this);
	}
	~A() { cout << "~A()" << endl; }
};
 
int main()
{
	shared_ptr<A> sp1(new A());
	shared_ptr<A> sp2 = sp1->getPtr();
	cout << "sp1.use_count() = " << sp1.use_count() << endl;
	cout << "sp2.use_count() = " << sp2.use_count() << endl;
}

在上面的代碼中,sp1 和 sp2 指針指向同一個對象,但是它們的引用計數為什么都是 1 呢?因為當你傳一個指針過去的時候,shared_ptr 根本就不知道是否已經有 shared_ptr 指向它,所以它會自己創建控制塊,最后導致同一對象會出現釋放兩次的情況。所以凡是需要共享使用類對象的地方,必須使用這個 shared_ptr 當作右值來構造或者拷貝構造(shared_ptr 類中定義了賦值運算符函數和拷貝構造函數)另一個 shared_ptr ,從而達到共享使用的目的

 


 

解決這個問題的方法是通過 weak_ptr ,我們只需要調用 shared_from_this() 函數,它通過 weak_ptr 來構造一個 shared_ptr。

 

我們可以做一個簡單的嘗試:

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    shared_ptr<int> sp1(new int(5));
    weak_ptr<int> wp(sp1);
    cout << "sp1.use_count() = " << sp1.use_count() << endl;
    shared_ptr<int> sp2(wp);
    cout << "sp2.use_count() = " << sp2.use_count() << endl;
    shared_ptr<int> sp3(wp);
    cout << "sp2.use_count() = " << sp3.use_count() << endl;
}


 

現在我們從源碼角度來分析它是如何實現的,我們查看 class enable_shared_from_this 的結構:

 

其中重點關注 _Ptr->_Wptr = shared_ptr<remove_cv_t<_Yty>>(_This, const_cast<remove_cv_t<_Yty> *>(_Ptr)) 這句代碼,它將調用下述代碼通過 _This 和 _Ptr 生成 shared_ptr。

 

最后一步是通過 _Weakly_construct_from 將剛才生成的shared_ptr 轉換成 weak_ptr。

 最后,當我們使用 shared_from_this() 時,就可以通過我們所保存的 weak_ptr 來生成 shared_ptr。

 

 現在,我們可以通過 enable_shared_from_this 來修改最初的程序:

#include <iostream>
#include <memory>
using namespace std;

class A: public std::enable_shared_from_this<A>
{
public:
	shared_ptr<A> getPtr() {
		return shared_from_this();
	}
	~A() { cout << "~A()" << endl; }
};
 
int main()
{
	shared_ptr<A> sp1(new A());
	shared_ptr<A> sp2 = sp1->getPtr();
	cout << "sp1.use_count() = " << sp1.use_count() << endl;
	cout << "sp2.use_count() = " << sp2.use_count() << endl;
}

  

 

 

References

  1. MSVC std::unique_ptr 源碼解析
  2. 講自己作為sharedptr傳出_c++ shared_ptr源代碼分析(from visual studio 2017)
  3. 《Effective Modern C++》學習筆記 - Item 19: 使用 std::shared_ptr 管理共享性資源(附MSVC源碼解析)
  4. 淺析 shared_ptr:MSVC STL 篇
  5. C++智能指針的使用與實現
  6. enable_shared_from_this用法分析


免責聲明!

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



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