簡介
boost::scoped_ptr是一個比較簡單的智能指針,它能保證在離開作用域之后它所管理對象能被自動釋放。下面這個例子將介紹它的使用:
1 #include <iostream> 2 #include <boost/scoped_ptr.hpp> 3 4 using namespace std; 5 6 class Book 7 { 8 public: 9 Book() 10 { 11 cout << "Creating book ..." << endl; 12 } 13 14 ~Book() 15 { 16 cout << "Destroying book ..." << endl; 17 } 18 }; 19 20 int main() 21 { 22 cout << "=====Main Begin=====" << endl; 23 { 24 boost::scoped_ptr<Book> myBook(new Book()); 25 } 26 cout << "===== Main End =====" << endl; 27 28 return 0; 29 }
運行結果:
可以看出:當myBook離開了它的作用域之后,它所管理的Book對象也隨之銷毀。
特點——不能共享控制權
scoped_ptr不能通過其他scoped_ptr共享控制權,因為在scoped_ptr類的內部將拷貝構造函數和=運算符重載定義為私有的。我們看下scoped_ptr類的定義就清楚了:
1 namespace boost 2 { 3 template<typename T> class scoped_ptr : noncopyable 4 { 5 private: 6 7 T *px; 8 9 scoped_ptr(scoped_ptr const &); 10 scoped_ptr &operator=(scoped_ptr const &); 11 12 typedef scoped_ptr<T> this_type; 13 14 void operator==( scoped_ptr const & ) const; 15 void operator!=( scoped_ptr const & ) const; 16 public: 17 explicit scoped_ptr(T *p = 0); 18 ~scoped_ptr(); 19 20 explicit scoped_ptr( std::auto_ptr<T> p ): px( p.release() ); 21 void reset(T *p = 0); 22 23 T &operator*() const; 24 T *operator->() const; 25 T *get() const; 26 27 void swap(scoped_ptr &b); 28 }; 29 30 template<typename T> 31 void swap(scoped_ptr<T> &a, scoped_ptr<T> &b); 32 }
下面這段代碼中的注釋部分打開會造成編譯失敗:
1 #include <iostream> 2 #include <boost/scoped_ptr.hpp> 3 4 using namespace std; 5 6 class Book 7 { 8 public: 9 Book() 10 { 11 cout << "Creating book ..." << endl; 12 } 13 14 ~Book() 15 { 16 cout << "Destroying book ..." << endl; 17 } 18 }; 19 20 int main() 21 { 22 cout << "=====Main Begin=====" << endl; 23 { 24 boost::scoped_ptr<Book> myBook(new Book()); 25 //boost::scoped_ptr<Book> myBook1(myBook); // Error: scoped_ptr的拷貝構造函數私有 26 //boost::scoped_ptr<Book> myBook2 = myBook; // Error: scoped_ptr的=運算符重載私有 27 } 28 cout << "===== Main End =====" << endl; 29 30 return 0; 31 }
所以,scoped_ptr不能用在標准庫的容器中,因為容器中的push_back操作需要調用scoped_ptr的=運算符重載函數,結果就是會導致編譯失敗。
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 #include <boost/scoped_ptr.hpp> 5 6 using namespace std; 7 8 class Book 9 { 10 private: 11 string name_; 12 13 public: 14 Book(string name) : name_(name) 15 { 16 cout << "Creating book " << name_ << " ..." << endl; 17 } 18 19 ~Book() 20 { 21 cout << "Destroying book " << name_ << " ..." << endl; 22 } 23 }; 24 25 int main() 26 { 27 cout << "=====Main Begin=====" << endl; 28 { 29 boost::scoped_ptr<Book> myBook(new Book("「1984」")); 30 vector<boost::scoped_ptr<Book>> vecScoped; 31 //vecScoped.push_back(myBook); // Error: push_back操作內部調用了scoped_ptr的=運算符重載函數 32 } 33 cout << "===== Main End =====" << endl; 34 35 return 0; 36 }
編譯檢查=萬無一失?
雖然我們無法通過scoped_ptr的拷貝構造函數和=運算符重載函數共享控制權。那如果將一個對象交給多個scoped_ptr來管理會怎樣?
1 #include <iostream> 2 #include <boost/scoped_ptr.hpp> 3 4 using namespace std; 5 6 class Book 7 { 8 public: 9 Book() 10 { 11 cout << "Creating book ..." << endl; 12 } 13 14 ~Book() 15 { 16 cout << "Destroying book ..." << endl; 17 } 18 }; 19 20 int main() 21 { 22 cout << "=====Main Begin=====" << endl; 23 { 24 Book * book = new Book(); 25 boost::scoped_ptr<Book> myBook(book); 26 boost::scoped_ptr<Book> myBook1(book); 27 } 28 cout << "===== Main End =====" << endl; 29 30 return 0; 31 }
我們發現編譯沒報錯,但是運行時出錯了,如下:
之所以會這樣是因為每個scoped_ptr對象都保存了自己所管理對象指針px,scoped_ptr對象在離開自己作用域時會調用了自身的析構函數,在析構函數內部會調用delete px,當多個scoped_ptr管理同一個對象時,那么在它們離開作用域之后,勢必會多次調用delete以釋放它們所管理的對象,從而造成程序運行出錯。
其他接口
雖然scoped_ptr不能轉移控制權,但是它們可以交換共享權。就以下面的代碼舉個例子:
1 #include <iostream> 2 #include <string> 3 #include <boost/scoped_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::scoped_ptr<Book> myBook(new Book("「1984」")); 29 boost::scoped_ptr<Book> myBook1(new Book("「A Song of Ice and Fire」")); 30 myBook.swap(myBook1); 31 } 32 cout << "===== Main End =====" << endl; 33 34 return 0; 35 }
運行結果:
根據棧的特性,應該是后面構造的scoped_ptr對象先銷毀(從而銷毀了它們所管理的對象),正是因為我們對兩個智能指針的控制權進行交換之后,才出現了這種相反的結果。
此外,在scoped_ptr離開作用域之前也是可以顯式銷毀它們所管理的對象的。調用它的reset方法即可。請看下面例子:
1 #include <iostream> 2 #include <string> 3 #include <boost/scoped_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::scoped_ptr<Book> myBook(new Book("「1984」")); 29 myBook.reset(); 30 cout << "After reset ..." << endl; 31 } 32 cout << "===== Main End =====" << endl; 33 34 return 0; 35 }
運行結果:
可以看出:程序在輸出“After reset ...”之前已經完成了對所管理對象的釋放。
總結(摘自《超越C++標准庫:Boost庫導論》)
使用裸指針來寫異常安全和無錯誤的代碼是很復雜的。使用智能指針來自動地把動態分配對象的生存期限制在一個明確的范圍之內,是解決這種問題的一個有效的方法,並且提高了代碼的可讀性、可維護性和質量。scoped_ptr明確地表示被指物不能被共享和轉移。當一個動態分配的對象被傳送給 scoped_ptr, 它就成為了這個對象的唯一的擁有者。因為scoped_ptr幾乎總是以自動變量或數據成員來分配的,因此它可以在離開作用域時正確地銷毀,從而在執行流由於返回語句或異常拋出而離開作用域時,總能釋放它所管理的內存。
在以下情況時使用scoped_ptr:
- 在可能有異常拋出的作用域里使用指針
- 函數里有幾條控制路徑
- 動態分配對象的生存期應被限制於特定的作用域內
- 異常安全非常重要時(始終如此!)
參考
- http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html
- Björn Karlsson:Beyond the C++ Standard Library: An Introduction to Boost(《超越C++標准庫:Boost庫導論》)
(完)