boost智慧指標-boost::share_ptr(使用原理以及分析)
簡介
boost::shared_ptr是可以共享所有權的指標。如果有多個shared_ptr共同管理同一個物件時,只有這些shared_ptr全部與該物件脫離關係之後,被管理的物件才會被釋放。通過下面這個例子先了解下shared_ptr的基本用法:
1 #include <iostream> 2 #include <string> 3 #include <boost/shared_ptr.hpp> 4 5 using namespace std; 6 7 class Book 8 { 9 private: 10 string name_; 11 12 public: 13 Book(string name) : name_(name) 14 { 15 cout << "Creating book " << name_ << " ..." << endl; 16 } 17 18 ~Book() 19 { 20 cout << "Destroying book " << name_ << " ..." << endl; 21 } 22 }; 23 24 int main() 25 { 26 cout << "=====Main Begin=====" << endl; 27 { 28 boost::shared_ptr<Book> myBook(new Book("「1984」")); 29 cout << "[From myBook] The ref count of book is " << myBook.use_count() << ".\n" << endl; 30 31 boost::shared_ptr<Book> myBook1(myBook); 32 cout << "[From myBook] The ref count of book is " << myBook.use_count() << "." << endl; 33 cout << "[From myBook1] The ref count of book is " << myBook1.use_count() << ".\n" << endl; 34 35 cout << "Reset for 1th time. Begin..." << endl; 36 myBook.reset(); 37 cout << "[From myBook] The ref count of book is " << myBook.use_count() << "." << endl; 38 cout << "[From myBook1] The ref count of book is " << myBook1.use_count() << "." << endl; 39 cout << "Reset for 1th time. End ...\n" << endl; 40 41 cout << "Reset for 2th time. Begin ..." << endl; 42 myBook1.reset(); 43 cout << "Reset for 2th time. End ..." << endl; 44 } 45 cout << "===== Main End =====" << endl; 46 47 return 0; 48 }
執行結果:
執行過程分析:
shared_ptr的管理機制其實並不複雜,就是對所管理的物件進行了引用計數,當新增一個shared_ptr對該物件進行管理時,就將該物件的引用計數加一;減少一個shared_ptr對該物件進行管理時,就將該物件的引用計數減一,如果該物件的引用計數為0的時候,說明沒有任何指標對其管理,才呼叫delete釋放其所佔的記憶體。
1) 建立Book物件,將其分配給myBook管理,此時其使用計數為1。
2) 將myBook的所有權共享給myBook1,檢視通過這2個shared_ptr檢視引用計數都為2。說明當所有權共享時,通過每個shared_ptr檢視到的引用計數值是一樣。
3) 剝奪myBook的所有權,通過myBook檢視到的引用計數值變為0(脫離關係,變為0),通過myBook檢視到的引用計數值變為1(共享者少了1個,減1)。
4) 當剝奪最後一個shared_ptr對其控制物件的所有權時,被管理的物件將被釋放。
內部實現
下面是boost::shared_ptr內部實現所涉及到的類關係圖(部分類屬性和成員函式省略):
shared_ptr<T>:px代表指向具體物件的指標;pn儲存引用計數相關資訊。shared_ptr的核心就是shared_count,因為整個shared_ptr實現都沒有出現對引用計數的具體操作,比如+1 -1等。而每一次需要用到對引用計數的操作都呼叫了shared_count內部封裝的函式,比如:swap、==、get_deleter、use_count等。
shared_count:內部包含sp_counted_base *pi_,該成員在shared_count的建構函式裡初始化。
1 // 建構函式(通過被管理物件型別構造) 2 template<class Y> explicit shared_count( Y * p ): pi_( 0 ) 3 { 4 ... 5 pi_ = new sp_counted_impl_p<Y>( p ); 6 ... 7 } 8 9 // 拷貝建構函式 10 shared_count(shared_count const & r): pi_(r.pi_) // nothrow 11 { 12 if( pi_ != 0 ) pi_->add_ref_copy(); 13 }
sp_count_base:該類主要對引用計數進行管理,used_count是被引用的此時;weak_count涉及到weak_ptr的相關內容,這裡就先不討論。add_ref_copy()內部會對used_count進行自加。release()內部呼叫dispose(),不過dispose()需要派生類自己實現。建立派生類物件時,會先初始化sp_count_base類物件中的use_count為1。
下面我們簡化下場景:建立一個名為myBook的shared_ptr物件來管理一個Book物件,再通過myBook新建名為myBook1的shared_ptr物件。通過如下時序圖來回顧下整個流程:
總結
與裸指標相比,shared_ptr 會有一點點額外的空間代價。我還沒有發現由於這些代價太大而需要另外尋找一個解決方案的情形。不要去建立你自己的引用計數智慧指標類。沒有比使用 shared_ptr 智慧指標更好的了。和前面介紹的boost::scoped_ptr相比,boost::shared_ptr可以共享物件的所有權,因此其使用範圍基本上沒有什麼限制,自然也可以使用在stl的容器中。另外它還是執行緒安全的,但boost::shared_ptr並不是絕對安全,下面幾條規則能使我們更加安全的使用boost::shared_ptr:
- 避免對shared_ptr所管理的物件的直接記憶體管理操作,以免造成該物件的重釋放。
- shared_ptr並不能對迴圈引用的物件記憶體自動管理(這點是其它各種引用計數管理記憶體方式的通病)。
- 不要構造一個臨時的shared_ptr作為函式的引數(有記憶體洩露風險,取決於編譯器廠商的實現)。
1 void f(shared_ptr<int>, int); 2 int g(); 3 4 void ok() 5 { 6 shared_ptr<int> p(new int(2)); 7 f(p, g()); 8 } 9 10 void bad() 11 { 12 f(shared_ptr<int>(new int(2)), g()); 13 }
參考