## scoped_ptr
### 頭文件: `"boost/scoped_ptr.hpp"`
`boost::scoped_ptr` 用于確保動態分配的對象能夠被正確地刪除。`scoped_ptr` 有著與`std::auto_ptr`類似的特性,而最大的區別在于它不能轉讓所有權而`auto_ptr`可以。事實上,`scoped_ptr`永遠不能被復制或被賦值!`scoped_ptr` 擁有它所指向的資源的所有權,并永遠不會放棄這個所有權。`scoped_ptr`的這種特性提升了我們的代碼的表現,我們可以根據需要選擇最合適的智能指針(`scoped_ptr` 或 `auto_ptr`)。
要決定使用`std::auto_ptr`還是`boost::scoped_ptr`, 就要考慮轉移所有權是不是你想要的智能指針的一個特性。如果不是,就用`scoped_ptr`. 它是一種輕量級的智能指針;使用它不會使你的程序變大或變慢。它只會讓你的代碼更安全,更好維護。
下面是`scoped_ptr`的摘要,以及其成員的簡要描述:
```
namespace boost {
template<typename T> class scoped_ptr : noncopyable {
public:
explicit scoped_ptr(T* p = 0);
~scoped_ptr();
void reset(T* p = 0);
T& operator*() const;
T* operator->() const;
T* get() const;
void swap(scoped_ptr& b);
};
template<typename T>
void swap(scoped_ptr<T> & a, scoped_ptr<T> & b);
}
```
### 成員函數
```
explicit scoped_ptr(T* p=0)
```
構造函數,存儲`p`的一份拷貝。注意,`p` 必須是用`operator new`分配的,或者是null. 在構造的時候,不要求`T`必須是一個完整的類型。當指針`p`是調用某個分配函數的結果而不是直接調用`new`得到的時候很有用:因為這個類型不必是完整的,只需要類型`T`的一個前向聲明就可以了。這個構造函數不會拋出異常。
```
~scoped_ptr()
```
刪除被指物。類型`T`在被銷毀時必須是一個完整的類型。如果`scoped_ptr`在它被析構時并沒有保存資源,它就什么都不做。這個析構函數不會拋出異常。
```
void reset(T* p=0);
```
重置一個 `scoped_ptr` 就是刪除它已保存的指針,如果它有的話,并重新保存 `p`. 通常,資源的生存期管理應該完全由`scoped_ptr`自己處理,但是在極少數時候,資源需要在`scoped_ptr`的析構之前釋放,或者`scoped_ptr`要處理它原有資源之外的另外一個資源。這時,就可以用`reset`,但一定要盡量少用它。(過多地使用它通常表示有設計方面的問題) 這個函數不會拋出異常。
```
T& operator*() const;
```
返回一個到被保存指針指向的對象的引用。由于不允許空的引用,所以解引用一個擁有空指針的`scoped_ptr`將導致未定義行為。如果不能肯定所含指針是否有效,就用函數`get`替代解引用。這個函數不會拋出異常。
```
T* operator->() const;
```
返回保存的指針。如果保存的指針為空,則調用這個函數會導致未定義行為。如果不能肯定指針是否空的,最好使用函數`get`。這個函數不會拋出異常。
```
T* get() const;
```
返回保存的指針。應該小心地使用`get`,因為它可以直接操作裸指針。但是,`get`使得你可以測試保存的指針是否為空。這個函數不會拋出異常。`get`通常在調用那些需要裸指針的函數時使用。
```
operator unspecified_bool_type() const
```
返回`scoped_ptr`是否為非空。返回值的類型是未指明的,但這個類型可被用于Boolean的上下文中。在if語句中最好使用這個類型轉換函數,而不要用`get`去測試`scoped_ptr`的有效性
```
void swap(scoped_ptr& b)
```
交換兩個`scoped_ptr`的內容。這個函數不會拋出異常。
### 普通函數
```
template<typename T> void swap(scoped_ptr<T>& a,scoped_ptr<T>& b)
```
這個函數提供了交換兩個scoped pointer的內容的更好的方法。之所以說它更好,是因為 `swap(scoped1,scoped2)` 可以更廣泛地用于很多指針類型,包括裸指針和第三方的智能指針。\[2\] `scoped1.swap(scoped2)` 則只能用于它的定義所在的智能指針,而不能用于裸指針。
> \[2\] 你可為那些不夠智能,沒有提供它們自己的交換函數的智能指針創建你的普通swap函數。
### 用法
`scoped_ptr`的用法與普通的指針沒什么區別;最大的差別在于你不必再記得在指針上調用`delete`,還有復制是不允許的。典型的指針操作(`operator*` 和 `operator->`)都被重載了,并提供了和裸指針一樣的語法。用`scoped_ptr`和用裸指針一樣快,也沒有大小上的增加,因此它們可以廣泛使用。使用`boost::scoped_ptr`時,包含頭文件`"boost/scoped_ptr.hpp"`. 在聲明一個`scoped_ptr`時,用被指物的類型來指定類模板的參數。例如,以下是一個包含`std::string`指針的`scoped_ptr`:
```
boost::scoped_ptr<std::string> p(new std::string("Hello"));
```
當`scoped_ptr`被銷毀時,它對它所擁有的指針調用`delete` 。
### 不需要手工刪除
讓我們看一個程序,它使用`scoped_ptr`來管理`std::string`指針。注意這里沒有對`delete`的調用,因為`scoped_ptr`是一個自動變量,它會在離開作用域時被銷毀。
```
#include "boost/scoped_ptr.hpp"
#include <string>
#include <iostream>
int main() {
{
boost::scoped_ptr<std::string>
p(new std::string("Use scoped_ptr often."));
// 打印字符串的值
if (p)
std::cout << *p << '\n';
// 獲取字符串的大小
size_t i=p->size();
// 給字符串賦新值
*p="Acts just like a pointer";
} // 這里p被銷毀,并刪除std::string
}
```
這段代碼中有幾個地方值得注明一下。首先,`scoped_ptr`可以測試其有效性,就象一個普通指針那樣,因為它提供了隱式轉換到一個可用于布爾表達式的類型的方法。其次,可以象使用裸指針那樣調用被指物的成員函數,因為重載了`operator->`. 第三,也可以和裸指針一樣解引用`scoped_ptr`,這歸功于`operator*`的重載。這些特性正是`scoped_ptr`和其它智能指針的用處所在,因為它們和裸指針的不同之處在于對生存期管理的語義上,而不在于語法上。
### 和auto_ptr幾乎一樣
`scoped_ptr` 與 `auto_ptr`間的區別主要在于對擁有權的處理。`auto_ptr`在復制時會從源`auto_ptr`自動交出擁有權,而`scoped_ptr`則不允許被復制。看看下面這段程序,它把`scoped_ptr` 和 `auto_ptr`放在一起,你可以清楚地看到它們有什么不同。
```
void scoped_vs_auto() {
using boost::scoped_ptr;
using std::auto_ptr;
scoped_ptr<std::string> p_scoped(new std::string("Hello"));
auto_ptr<std::string> p_auto(new std::string("Hello"));
p_scoped->size();
p_auto->size();
scoped_ptr<std::string> p_another_scoped=p_scoped;
auto_ptr<std::string> p_another_auto=p_auto;
p_another_auto->size();
(*p_auto).size();
}
```
這個例子不能通過編譯,因為`scoped_ptr`不能被復制構造或被賦值。`auto_ptr`既可以復制構造也可以賦值,但這們同時也意味著它把所有權從`p_auto` 轉移給了 `p_another_auto`, 在賦值后`p_auto`將只剩下一個空指針。這可能會導致令人不快的驚訝,就象你試圖把`auto_ptr`放入容器內時所發生的那樣。\[3\] 如果我們刪掉對`p_another_scoped`的賦值,程序就可以編譯了,但它的運行結果是不可預測的,因為它解引用了`p_auto`里的空指針`(*p_auto)`.
> \[3\] 永遠不要把`auto_ptr`放入標準庫的容器里。如果你試一下,通常你會得到一個編譯錯誤;如果你沒有得到錯誤,你就麻煩了。
由于`scoped_ptr::get`會返回一個裸指針,所以就有可能對`scoped_ptr`做一些有害的事情,其中有兩件是你尤其要避免的。第一,不要刪除這個裸指針。因為它會在`scoped_ptr`被銷毀時再一次被刪除。第二,不要把這個裸指針保存到另一個`scoped_ptr` (或其它任何的智能指針)里。因為這樣也會兩次刪除這個指針,每個`scoped_ptr`一次。簡單地說,盡量少用`get`, 除非你要使用那些要求你傳送裸指針的遺留代碼!
### scoped_ptr 和Pimpl用法
`scoped_ptr`可以很好地用于許多以前使用裸指針或`auto_ptr`的地方,如在實現pimpl用法時。\[4\]pimpl 用法背后的思想是把客戶與所有關于類的私有部分的知識分隔開。由于客戶是依賴于類的頭文件的,頭文件中的任何變化都會影響客戶,即使僅是對私有節或保護節 的修改。pimpl用法隱藏了這些細節,方法是將私有數據和函數放入一個單獨的類中,并保存在一個實現文件中,然后在頭文件中對這個類進行前向聲明并保存 一個指向該實現類的指針。類的構造函數分配這個pimpl類,而析構函數則釋放它。這樣可以消除頭文件與實現細節的相關性。我們來構造一個實現pimpl 用法的類,然后用智能指針讓它更為安全。
> \[4\] 這也被稱為Cheshire Cat 用法. 關于pimpl用法更多的說明請見 [www.gotw.ca/gotw/024.htm](http://www.gotw.ca/gotw/024.htm) 和 Exceptional C++ 。
```
// pimpl_sample.hpp
#if !defined (PIMPL_SAMPLE)
#define PIMPL_SAMPLE
class pimpl_sample {
struct impl; // 譯者注:原文中這句在class之外,與下文的實現代碼有矛盾
? impl* pimpl_;
public:
pimpl_sample();
~pimpl_sample();
void do_something();
};
#endif
```
這是`pimpl_sample`類的接口。`struct impl` 是一個前向聲明,它把所有私有成員和函數放在另一個實現文件中。這樣做的效果是使客戶與`pimpl_sample`類的內部細節完全隔離開來。
```
// pimpl_sample.cpp
#include "pimpl_sample.hpp"
#include <string>
#include <iostream>
struct pimpl_sample::impl {
void do_something_() {
std::cout << s_ << "\n";
}
std::string s_;
};
pimpl_sample::pimpl_sample()
: pimpl_(new impl) {
pimpl_->s_ = "This is the pimpl idiom";
}
pimpl_sample::~pimpl_sample() {
delete pimpl_;
}
void pimpl_sample::do_something() {
pimpl_->do_something_();
}
```
看起來很完美,但并不是的。這個實現不是異常安全的!原因是`pimpl_sample`的構造函數有可能在`pimpl`被構造后拋出一個異常。在構造函數中拋出異常意味著已構造的對象并不存在,因此在棧展開時將不會調用它的析構函數。這樣就意味著分配給`pimpl_`指針的內存將泄漏。然而,有一樣簡單的解決方法:用`scoped_ptr`來解救!
```
class pimpl_sample {
struct impl;
boost::scoped_ptr<impl> pimpl_;
...
};
```
讓`scoped_ptr`來處理隱藏類`impl`的生存期管理,并從析構函數中去掉對`impl`的刪除(它不再需要,這要感謝`scoped_ptr`),這樣就做完了。但是,你必須記住要手工定義析構函數;原因是在編譯器生成隱式析構函數時,類`impl`還是不完整的,所以它的析構函數不能被調用。如果你用`auto_ptr`來保存`impl`, 你可以編譯,但也還是有這個問題,但如果用`scoped_ptr`, 你將收到一個錯誤提示。
要注意的是,如果你使用`scoped_ptr`作為一個類的成員,你就必須手工定義這個類的復制構造函數和賦值操作符。原因是`scoped_ptr`是不能復制的,因此聚集了它的類也變得不能復制了。
最后一點值得注意的是,如果pimpl實例可以安全地被多個封裝類(在這里是`pimpl_sample`)的實例所共享,那么用`boost::shared_ptr`來管理pimpl的生存期才是正確的選擇。用`shared_ptr`比用`scoped_ptr`的優勢在于,不需要手工去定義復制構造函數和賦值操作符,而且可以定義空的析構函數,`shared_ptr`被設計為可以正確地用于未完成的類。
### scoped_ptr 不同于 const auto_ptr
留心的讀者可能已經注意到`auto_ptr`可以幾乎象`scoped_ptr`一樣地工作,只要把`auto_ptr`聲明為`const`:
```
const auto_ptr<A> no_transfer_of_ownership(new A);
```
它們很接近,但不是一樣。最大的區別在于`scoped_ptr`可以被`reset`, 在需要時可以刪除并替換被指物。而對于`const auto_ptr`這是不可能的。另一個小一點的區別是,它們的名字不同:盡管`const auto_ptr`意思上和`scoped_ptr`一樣,但它更冗長,也更不明顯。當你的詞典里有了`scoped_ptr`,你就應該使用它,因為它可以更清楚地表明你的意圖。如果你想說一個資源是要被限制在作用域里的,并且不應該有辦法可以放棄它的所有權,你就應該用 `boost::scoped_ptr`.
### 總結
使用裸指針來寫異常安全和無錯誤的代碼是很復雜的。使用智能指針來自動地把動態分配對象的生存期限制在一個明確的范圍之內,是解決這種問題的一個有效方法,并且提高了代碼的可讀性、可維護性和質量。`scoped_ptr` 明確地表示被指物不能被共享和轉移。正如你所看到的,`std::auto_ptr`可以從另一個`auto_ptr`那里竊取被指物,那怕是無意的,這被認為是`auto_ptr`的最大缺點。正是這個缺點使得`scoped_ptr`成為`auto_ptr`最好的補充。當一個動態分配的對象被傳送給`scoped_ptr`, 它就成為了這個對象的唯一的擁有者。因為`scoped_ptr`幾乎總是以自動變量或數據成員來分配的,因此它可以在離開作用域時正確地銷毀對象,從而在執行流由于返回語句或異常拋出而離開作用域時,也總能釋放它所管理的內存。
在以下情況時使用 `scoped_ptr` :
* 在可能有異常拋出的作用域里使用指針
* 函數里有幾條控制路徑
* 動態分配對象的生存期應被限制于特定的作用域內
* 異常安全非常重要時(總應如此!)
- 序
- 前言
- Acknowledgments
- 關于作者
- 本書的組織結構
- Boost的介紹
- 字符串及文本處理
- 數 據結構, 容器, 迭代器, 和算法
- 函數對象及高級編程
- 泛 型編程與模板元編程
- 數學及數字處理
- 輸入/輸出
- 雜項
- Part I: 通用庫
- Library 1. Smart_ptr
- Smart_ptr庫如何改進你的程序?
- 何時我們需要智能指針?
- Smart_ptr如何適應標準庫?
- scoped_ptr
- scoped_array
- shared_ptr
- shared_array
- intrusive_ptr
- weak_ptr
- Smart_ptr總結
- Library 2. Conversion
- Conversion 庫如何改進你的程序?
- polymorphic_cast
- polymorphic_downcast
- numeric_cast
- lexical_cast
- Conversion 總結
- Library 3. Utility
- Utility 庫如何改進你的程序?
- BOOST_STATIC_ASSERT
- checked_delete
- noncopyable
- addressof
- enable_if
- Utility 總結
- Library 4. Operators
- Operators庫如何改進你的程序?
- Operators
- 用法
- Operators 總結
- Library 5. Regex
- Regex庫如何改進你的程序?
- Regex 如何適用于標準庫?
- Regex
- 用法
- Regex 總結
- Part II: 容器及數據結構
- Library 6. Any
- Any 庫如何改進你的程序?
- Any 如何適用于標準庫?
- Any
- 用法
- Any 總結
- Library 7. Variant
- Variant 庫如何改進你的程序?
- Variant 如何適用于標準庫?
- Variant
- 用法
- Variant 總結
- Library 8. Tuple
- Tuple 庫如何改進你的程序?
- Tuple 庫如何適用于標準庫?
- Tuple
- 用法
- Tuple 總結
- Part III: 函數對象與高級編程
- Library 9. Bind
- Bind 庫如何改進你的程序?
- Bind 如何適用于標準庫?
- Bind
- 用法
- Bind 總結
- Library 10. Lambda
- Lambda 庫如何改進你的程序?
- Lambda 如何適用于標準庫?
- Lambda
- 用法
- Lambda 總結
- Library 11. Function
- Function 庫如何改進你的程序?
- Function 如何適用于標準庫?
- Function
- 用 法
- Function 總結
- Library 12. Signals
- Signals 庫如何改進你的程序?
- Signals 如何適用于標準庫?
- Signals
- 用法
- Signals 總結