shared_ptr智能指针源码剖析
前幾天有個人問了我一個問題: 如何將一個智能指針作為函數的返回值傳遞出來。當時這個問題一下子把我問倒了,后來經人提醒有一個叫shared_ptr的智能指針可以解決這個問題。
將shared_ptr作為函數返回值的代碼是這樣的:
#include <tr1/memory> #include <stdio.h>using std::tr1::shared_ptr;shared_ptr<int> ReturnSharedPtr() {shared_ptr<int> p(new int(1000));return p; }int main() {shared_ptr<int> p1 = ReturnSharedPtr();printf("%d\n", *p1);return 0; }
在g++4.3版本以上編譯通過。shared_ptr頭文件的位置有點古怪,在我的DEBIAN(squeeze)機器上的這個地方:/usr/include/c++/4.4/tr1/shared_ptr.h,所以使用時要
#include <tr1/memory> 而且shared_ptr被包裝在std::tr1這個名字空間內。所以使用的時候和一般的stl模板的方式不大一樣, 要using std::tr1這個namespace。也可以用一個編譯器選項去掉這種古怪的定義方法, 具體是那個選項我查不到了。請了解的人幫忙指正一下。
shared_ptr的實現
看了一下stl的源碼,shared_ptr的實現是這樣的:? shared_ptr模板類有一個__shared_count類型的成員_M_refcount來處理引用計數的問題。__shared_count也是一個模板類,它的內部有一個指向Sp_counted_base_impl類型的指針_M_pi。所有引用同一個對象的shared_ptr都共用一個_M_pi指針。
當一個shared_ptr拷貝復制時, _M_pi指針調用_M_add_ref_copy()函數將引用計數+1。 當shared_ptr析構時,_M_pi指針調用_M_release()函數將引用計數-1。 _M_release()函數中會判斷引用計數是否為0. 如果引用計數為0, 則將shared_ptr引用的對象內存釋放掉。
這是__shared_count拷貝復制時的代碼。首先將參數__r的_M_pi指針賦值給自己, 然后判斷指針是否為NULL, 如果不為null 則增加引用計數。COSTA_DEBUG_REFCOUNT和COSTA_DEBUG_SHAREDPTR是我為了打印引用計數的調試代碼,會打印文件行號和當前引用計數的值。
下面是我寫的一段簡單的測試代碼:
下面是運行結果:
shared_ptr.h 169行是__shared_count拷貝構造時增加引用計數,184行是__shared_count賦值操作,161行是__share_count的析構時減少引用計數, 79行是釋放引用對象的內存。
shared_ptr線程安全性問題
關于shared_ptr的線程安全性。查了一些網上的資料,有的說是安全的,有的說不安全。引用CSDN上一篇比較老的帖子, 它是這樣說的:
“Boost?文檔對于?shared_ptr?的線程安全有一段專門的記述,內容如下:
shared_ptr?objects?offer?the?same?level?of?thread?safety?as?built-in?types.?A?shared_ptr?instance?can?be?"read"?(accessed?using?only?const?operations)?simultaneously?by?multiple?threads.?Different?shared_ptr?instances?can?be?"written?to"?(accessed?using?mutable?operations?such?as?operator=?or?reset)?simultaneosly?by?multiple?threads?(even?when?these?instances?are?copies,?and?share?the?same?reference?count?underneath.)
Any?other?simultaneous?accesses?result?in?undefined?behavior.
翻譯為中文如下:
shared_ptr?對象提供與內建類型一樣的線程安全級別。一個?shared_ptr?實例可以同時被多個線程“讀”(僅使用不變操作進行訪問)。 不同的?shared_ptr?實例可以同時被多個線程“寫入”(使用類似?operator=?或?reset?這樣的可變操作進行訪問)(即使這些實 例是拷貝,而且共享下層的引用計數)。
任何其它的同時訪問的結果會導致未定義行為。”
這幾句話比較繁瑣,我總結一下它的意思:
1 同一個shared_ptr被多個線程“讀”是安全的。
2 同一個shared_ptr被多個線程“寫”是不安全的。
3 共享引用計數的不同的shared_ptr被多個線程”寫“ 是安全的。
如何印證上面的觀點呢?
其實第一點我覺得比較多余。因為在多個線程中讀同一個對象,在正常情況下不會有什么問題。
所以問題就是:如何寫程序證明同一個shared_ptr被多個線程"寫"是不安全的?
我的思路是,在多個線程中同時對一個shared_ptr循環執行兩遍swap。 shared_ptr的swap函數的作用就是和另外一個shared_ptr交換引用對象和引用計數,是寫操作。執行兩遍swap之后, shared_ptr引用的對象的值應該不變。
程序如下:
這個程序中我啟了10個線程。每個線程調用10萬次 CostaSwapSharedPtr2函數。 在CostaSwapSharePtr2函數中,對同一個share_ptr全局變量gp進行兩次swap(寫操作), 在函數返回之后檢查gp的值是否被修改。如果gp值被修改,則證明多線程對同一個share_ptr執行寫操作是不安全的。
程序運行的結果如下:
Thread error. *gp=1000 Thread error. *gp=1000 Thread quit Thread quit Thread error. *gp=1000 Thread quit Thread error. *gp=1000 Thread quit Thread error. *gp=1000 Thread quit Thread error. *gp=1000 Thread quit Thread error. *gp=1000 Thread quit Thread error. *gp=1000 Thread quit Thread error. *gp=1000 Thread quit Thread quit 10個線程有9個出錯。證明多線程對同一個share_ptr執行寫操作是不安全的。我們在程序中,如果不運行CostaSwapSharedPtr2, 改成運行CostaSwapSharedPtr1呢?? CostaSwapSharedPtr1和CostaSwapSharedPtr2的區別在于, 它不是直接對全局變量gp進行寫操作,而是將gp拷貝出來一份再進行寫操作。運行的結果如下: costa@pepsi:~/test/cpp/shared_ptr$ ./b Thread quit Thread quit Thread quit Thread quit Thread quit Thread quit Thread quit Thread quit Thread quit Thread quit跑了很多次都沒有出錯。說明共享引用計數的不同的shared_ptr執行swap是線程安全的。BOOST文檔是可信的。
補充一個問題:?為什么shared_ptr可以作為STL標準容器的元素,而auto_ptr不可以
這篇文章小結一下:
1 shared_ptr是一個非常實用的智能指針。
2 shared_ptr的實現機制是在拷貝構造時使用同一份引用計數。
3 對同一個shared_ptr的寫操作不是線程安全的。 對使用同一份引用計數的不同shared_ptr是線程安全的。
總結
以上是生活随笔為你收集整理的shared_ptr智能指针源码剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 存储类型auto,static,exte
- 下一篇: libxml2如何解析xml格式的字符串