c++ shared_ptr


  • shared_ptr是通過指針保持對象共享所有權的智能指針。多個shared_ptr對象可占有同一資源,當最后一個shared_ptr對象被銷毀或者通過operator=,reset()操作賦予另一指針時,其管理的資源才會被回收。
  • 管理同一資源的不同shared_ptr對象能在不同線程中不加同步的調用其所有成員函數。當然這里指的是shared_ptr對象本身的成員函數,如果你想多線程訪問其管理的資源,那么並不會有這種保證。
  • 其成員類型、成員函數與成員變量等在標准中十分明確,在此不再贅述:https://en.cppreference.com/w...
  • shared_ptr也可以指定刪除器,但與unique_ptr不同的是,該刪除器類型並不作為shared_ptr模板中的參數之一。
  • C++17之前,shared_ptr管理動態分配的數組需要提供自定義的刪除器。c++17可以管理動態數組,例如shared_ptr<int[]> sp(new int[10])。為了支持這一點,element_type現在被定義為remove_extent_t<T>。
  • 話不多說,我們來看它的源代碼實現:

  • 與unique_ptr不同,shared_ptr並未對管理數組對象特化一個版本。

  • 這個類沒有成員變量,其數據存儲於基類_Ptr_base<_Ty>中,在其成員函數中絕大部分操作也是調用基類提供的函數完成。
  • 接下來我們分析一下_Ptr_base基類的實現。_Ptr_base是一個模板類,模板參數是shared_ptr管理的指針對應的類型。該基類擁有如下這兩個數據成員:

  • 關於element_type :

  • 可以看到element_type 就是模板參數對應的標量類型(scalar type)(since c++17)。
  • 關於_Ref_count_base:

  • 從名字上就可以看出來,這是一個引用計數的基類,其有兩個純虛函數_Destroy()和_Delete_this(),並且擁有兩個數據成員_Uses和_Weaks,其類型都為_Atomic_counter_t,shared_ptr之所以在多線程條件下可以不加同步的訪問,就是因為其內部的引用計數的變化都是用原子操作實現的。這個基類還有對應的對_Uses和_Weaks操作的函數,這些函數保證了在多線程條件下修改引用計數的原子性,我們放到最后分析。
  • 繼承自該類的有五個類,我們研究前三個(后兩個派生類目前沒有發現是干嘛用的。。//第一次更新:和make_shared相關,之后分析):

 

  • 從模板參數可以看出來,這三個類分別代表了采用默認刪除器和默認內存分配器的版本、采用自定義刪除器和默認內存分配器的版本和采用自定義的刪除器和內存分配器的版本。我們先來分析較為常用的_Ref_count,即采用默認的刪除器和內存分配器的版本的類:

  • 這個類非常簡潔,由於采用默認的刪除和內存分配操作,因此只需要保留一個_Ty*類型的指針即可。
  • 第二個派生類_Ref_count_resource:

  • 這個類中我們見到了一個老朋友:_Compressed_pair。詳見unique_ptr那一節中的講解,這里不再贅述。
  • 第三個派生類:

  • 可以看到,這里有一個嵌套的_Compressed_pair。因為刪除器和內存分配器都很有可能是空類。
  • 這些類具體的不同主要在兩個虛函數中如何釋放資源以及如何釋放自己,這里不再贅述,應該很容易看懂。我們看一下這些派生類是什么時候生成並被賦值給_Ptr_base基類的_Rep成員的。跳回shared_ptr的構造函數(第一次更新,這里之后會加上_SP_convertible的部分):

  • 以只接收一個指針_Ux*的構造函數為例,該函數內部調用了_Setp函數。這里的is_array<_Ty>{}生成了一個臨時對象,大家在源代碼里跳過去能看到這里是做了一個函數選擇,利用函數重載的功能,根據_Ty是否是一個數組類型將其分發給對應的重載函數。這種技巧在stl庫中應用的很多,具體名字被我給忘了。。。這里不多解釋,接着看。

  • _Setp確實有兩個重載函數,如果_Ty是一個數組類型,繼續調用_Setpd(_Px,default_delete<_Ux[]>{})。這里根據_Ty的類型我們選擇了正確的刪除器類型。

  • 在_Setpd函數中我們看到了_Ref_count_resource這個類的動態生成。
  • 如果_Ty不是一個數組類型呢?我們看一下_Setp的另一個版本,其中出現了_Ref_count這個類的動態生成。
  • 兩個版本均繼續調用了_Set_ptr_rep_and_enable_shared函數,區別就是生成的引用計數類不一樣。現在我們搞清楚了大致流程:根據構造函數的不同、模板參數的不同,shared_ptr生成對應的引用計數派生類傳入底層函數。_Set_ptr_rep_and_enable_shared這個函數后續做了哪些工作,因為涉及到了weak_ptr和enable_shared_from_this這些類,這里一時半會說不清楚。目前我們只要知道將_Px和_Dt賦值給了基類中那兩個成員變量指針即可。
  • 到目前為止我們能夠明白:不同的shared_ptr對象之所以能夠共享資源,是因為其每個對象都有一個指向該資源的指針_Ptr和一個指向引用計數類的指針_Rep,根據刪除和內存分配等不同要求引用計數類有多個派生類,在構造shared_ptr時會根據情況生成對應的派生類。
  • 我們以一個拷貝構造函數為例,看shared_ptr是如何通過調用其基類提供的函數修改引用計數,達到共享資源的目的的:

  • 如果_Other._Rep不為空指針,則調用其_Incref()函數,然后對_Ptr和_Rep進行賦值,_Incref()這個函數看名字其實能看出來,就是增加引用計數,前面提到過,這些函數能保證在不同線程中原子的修改引用計數,我們看下內部的實現過程:

  • 再往下就是和平台實現及其密切的原生API了,我們的分析都到這一步為止。
  • 我們再看一下shared_ptr的析構函數:

  • 析構函數中,如果_Uses減1之后等於0,則調用_Destroy()函數釋放資源,並調用_Decwref()函數對弱引用計數減1,由於弱引用的存在,哪怕資源已經釋放,也有可能有弱引用綁定到引用計數類上,所以這里不能直接釋放引用計數類的資源,而是判斷弱引用計數是否為0,如果_Weaks也為0,那么可以釋放引用計數類的資源,調用_Delete_this()。(注:這里對弱引用計數進行自減一操作,后面會解釋為什么)


免責聲明!

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



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