c++ weak_ptr源代碼分析(from visual studio 2017)

  • weak_ptr是一種持有被shared_ptr管理者的資源的弱引用的智能指針。它必須通過轉化爲shared_ptr來訪問管理的資源。
  • weak_ptr被用來跟蹤資源,它通過轉化爲shared_ptr來獲取臨時所有權。如果這個時候原先擁有資源的shared_ptr銷燬了,資源的生命週期將會被延長至這個轉化得到的shared_ptr析構之前。
  • weak_ptr另外一個作用是打破shared_ptr可能的循環引用(循環引用會導致應該釋放的資源沒有釋放,此處不展開)。
  • weak_ptr的成員函數等信息見標準:

https://en.cppreference.com/w...

  • weak_ptr的源代碼:

58.png

  • weak_ptr是一個模板類,模板參數同shared_ptr一樣,爲跟蹤資源的對應類型。換言之,weak_ptr和shared_ptr擁有同樣的數據成員。只不過其成員函數在修改查詢引用計數類中不同的數據罷了。

59.png

  • 不知道大家記不記得,引用計數基類中有兩個數據成員:_Uses和_Weaks。_Uses則是我們理解的引用計數,每有一個shared_ptr共享資源,_Uses就會加一,反之每一個shared_ptr析構,_Uses就會減一,當_Uses變爲零時,意味着沒有shared_ptr再佔有資源,這個時候佔有的資源會調用釋放操作。但是並不能直接銷燬引用計數對象,因爲可能有弱引用還綁定到引用計數對象上。
  • 而_Weaks就是weak_ptr追蹤資源的計數器,每有一個weak_ptr追蹤資源,_Weaks就會加一,反之每一個weak_ptr析構時,_Weaks就會減一,當_Weaks變爲零時,意味着沒有weak_ptr再追蹤資源,這時會銷燬引用計數對象。(注:不會出現_Weaks變爲零而_Uses不爲零的情況,因爲引用計數對象在構造的時候,_Weaks被初始化爲1)

60.png

  • 只要使用過weak_ptr,就不會對這兩個函數陌生,expired()用來判斷是否有對應的shared_ptr佔有資源。lock()返回用該weak_ptr構造的shared_ptr對象,我們可以通過這個shared_ptr對象操縱資源。如果在調用lock時,該資源已經被釋放,那麼返回的shared_ptr將會是empty的(A shared_ptr may also own no objects, in which case it is called empty (an empty shared_ptr may have a non-null stored pointer if the aliasing constructor was used to create it))。
  • lock函數內部先定義了一個空的shared_ptr指針,並調用了其內部函數_Construct_from_weak,該函數的參數是weak_ptr,這裏把自己傳了進去。

61.png

  • 這個函數內部檢查了_Other._Rep是不是一個空指針,如果不是的話,調用_Other._Rep->_Incref_nz()並判斷其返回值,如果是true的話,用這個weak_ptr內部的數據成員賦值給本shared_ptr,完成構造並返回true,否則返回false。
  • 可以想象,_Incref_nz()做了以下這些事:
  1. 如果weak_ptr追蹤的資源這時候已經被釋放,則該函數返回false。
  2. 如果weak_ptr追蹤的資源還沒有被釋放,則其引用計數_Uses自增一,返回true。
  • 如果在單線程條件下,這段代碼應該並不難寫,但是要保證不同線程中調用的正確性,則需要了解一些多線程編程的技巧。

62.png

  • 這段代碼在一個無限循環的條件下,首先獲取_Uses的值,(這裏需要static_cast轉換爲volatile _Atomic_counter_t&,爲了限制編譯器對其進行優化,volatile使得每次讀取_Uses時,必須從內存中重新讀取,而不是使用保存在寄存器裏的備份。)如果這時_Uses已經變爲0,則意味着資源已經被釋放,返回false。否則我們調用_InterlockedCompareExchange函數來原子的對_Uses加一。
  • _InterlockedCompareExchange是多線程編程中compare_exchange原語的一種實現,該函數有三個參數,第一個參數我們稱爲當前值,第二個參數稱爲設定值,第三個參數稱爲期望值。噹噹前值等於期望值時,函數將當前值設置爲設定值,並返回當前值原先的值。噹噹前值不等於期望值時,函數不會進行任何操作,只返回當前值的值,整個操作是原子的。
  • 這裏涉及到多線程編程中原子變量的知識,大家可以去找相關的書去看,一兩句話解釋不清楚,總之,這樣的操作可以保證多個線程中調用這個函數時,能正確的修改引用計數。而之所以把這段代碼放入for(;;)這個無限循環中執行,因爲可能有其他線程中的對象也在修改_Uses,故_InterlockedCompareExchange並不保證每次都能執行成功。(例如其他線程在我們獲取_Uses的值到調用_InterlockedCompareExchange之前修改了_Uses)
  • 其實weak_ptr的實現很簡潔,重點分析的除了lock操作,還有我認爲就是和enable_shared_from_this相關的部分了。關於enable_shared_from_this我們放到下一節講。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章