# Item 8: 防止因為 exceptions(異常)而離開 destructors(析構函數)
作者:Scott Meyers
譯者:fatalerror99 (iTePub's Nirvana)
發布:http://blog.csdn.net/fatalerror99/
C++ 并不禁止從 destructors(析構函數)中引發 exceptions(異常),但是它堅決地阻止這樣的實踐。至于有什么好的理由,考慮:
```
class Widget {
public:
...
~Widget() { ... } // assume this might emit an exception
};
void doSomething()
{
std::vector<Widget> v;
...
} // v is automatically destroyed here
```
當 vector v 被析構時,它有責任析構它包含的所有 Widgets。假設 v 中有十個 Widgets,在第一個的析構過程中,拋出一個 exception(異常)。其它 9 個 Widgets 仍然必須被析構(否則它們持有的所有資源將被泄漏),所以 v 應該調用它們的 destructors(析構函數)。但是假設在這個調用期間,第二個 Widget 的 destructors(析構函數)又拋出一個 exception(異常)。現在同時有兩個活動的 exceptions(異常),對于 C++ 來說,這太多了。在非常巧合的條件下產生這樣兩個同時活動的異常,程序的執行會終止或者引發 undefined behavior(未定義行為)。在本例中,將引發 undefined behavior(未定義行為)。使用任何其它的標準庫 container(容器)(比如,list,set),任何 TR1(參見 Item 54)中的 container(容器),甚至是一個 array(數組),都可能會引發同樣的 undefined behavior(未定義行為)。也并非必須是 containers(容器)或 arrays(數組)才會陷入麻煩。程序過早終止或 undefined behavior(未定義行為)是 destructors(析構函數)引發 exceptions(異常)的結果,即使沒有使用 containers(容器)和 arrays(數組)也會如此。C++ 不喜歡引發 exceptions(異常)的 destructors(析構函數)。
這比較容易理解,但是如果你的 destructor(析構函數)需要執行一個可能失敗而拋出一個 exception(異常)的操作,該怎么辦呢?例如,假設你與一個數據庫連接類一起工作:
```
class DBConnection {
public:
...
static DBConnection create(); // function to return
// DBConnection objects; params
// omitted for simplicity
void close(); // close connection; throw an
}; // exception if closing fails
```
為了確保客戶不會忘記在 DBconnection objects(對象)上調用 close,一個合理的主意是為 DBConnection 建立一個 resource-managing class(資源管理類),在它的 destructor(析構函數)中調用 close。這樣的 resource-managing classes(資源管理類)將在 Chapter 3(第三章)中一探究竟,但在這里,只要認為這樣一個 class(類)的 destructor(析構函數)看起來像這樣就足夠了:
```
class DBConn { // class to manage DBConnection
public: // objects
...
~DBConn() // make sure database connections
{ // are always closed
db.close();
}
private:
DBConnection db;
};
```
它允許客戶像這樣編程:
```
{ // open a block
DBConn dbc(DBConnection::create()); // create DBConnection object
// and turn it over to a DBConn
// object to manage
... // use the DBConnection object
// via the DBConn interface
} // at end of block, the DBConn
// object is destroyed, thus
// automatically calling close on
// the DBConnection object
```
只要能成功地調用 close 就可以了,但是如果這個調用導致一個 exception(異常),DBConn 的 destructor(析構函數)將傳播那個 exception(異常),也就是說,它將離開 destructor(析構函數)。這就產生了問題,因為 destructor(析構函數)拋出了一個燙手的山芋。
有兩個主要的方法避免這個麻煩。DBConn 的 destructor(析構函數)能:
* Terminate the program if close tHRows(如果 close 拋出異常就終止程序),一般是通過調用 abort:
```
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
make log entry that the call to close failed;
std::abort();
}
}
```
如果在析構的過程遭遇到錯誤后程序不能繼續運行,這就是一個合理的選擇。它有一個好處是:如果允許從 destructor(析構函數)傳播 exception(異常)可能會導致 undefined behavior(未定義行為),這樣就能防止它發生。也就是說,調用 abort 就可以預先防止 undefined behavior(未定義行為)。
* Swallow the exception arising from the call to close(抑制這個對 close 的調用造成的異常):
```
DBConn::~DBConn()
{
try { db.close(); }
catch (...) {
make log entry that the call to close failed;
}
}
```
通常,swallowing exceptions(抑制異常)是一個不好的主意,因為它會隱瞞重要的信息—— something failed(某事失敗了)!然而,有些時候,swallowing exceptions(抑制異常)比冒程序過早終止或 undefined behavior(未定義行為)的風險更可取。程序必須能夠在遭遇到一個錯誤并忽略之后還能繼續可靠地運行,這才能成為一個可行的選擇。
這些方法都不太吸引人。它們的問題首先在于程序無法對引起 close 拋出 exception(異常)的條件做出回應。
一個更好的策略是設計 DBConn 的 interface(接口),以使它的客戶有機會對可能會發生的問題做出回應。例如,DBConn 能夠自己提供一個 close 函數,從而給客戶一個機會去處理從那個操作中發生的 exception(異常)。它還能保持對它的 DBConnection 是否已被 closed 的跟蹤,如果沒有就在 destructor(析構函數)中自己關閉它。這樣可以防止連接被泄漏。如果在 DBConnection(原文如此,嚴重懷疑此處應為 DBConn ——譯者注)的 destructor(析構函數)中對 close 的調用失敗,無論如何,我們還可以再返回到終止或者抑制。
```
class DBConn {
public:
...
void close() // new function for
{ // client use
db.close();
closed = true;
}
~DBConn()
{
if (!closed) {
try { // close the connection
db.close(); // if the client didn't
}
catch (...) { // if closing fails,
make log entry that call to close failed; // note that and
... // terminate or swallow
}
}
private:
DBConnection db;
bool closed;
};
```
將調用 close 的責任從 DBConn 的 destructor(析構函數)移交給 DBConn 的客戶(同時在 DBConn 的 destructor(析構函數)中包含一個“候補”調用)可能會作為一種肆無忌憚地推卸責任的做法而使你吃驚。你甚至可以把它看作對 Item 18 中關于使 interfaces(接口)易于正確使用的建議的違背。實際上,這都不正確。如果一個操作可能失敗而拋出一個 exception(異常),而且可能有必要處理這個 exception(異常),這個 exception(異常)就 has to come from some non-destructor function(必須來自非析構函數)。這是因為 destructor(析構函數)引發 exception(異常)是危險的,永遠都要冒著程序過早終止或 undefined behavior(未定義行為)的風險。在本例中,讓客戶自己調用 close 并不是強加給他們的負擔,而是給他們一個時機去應付錯誤,否則他們將沒有機會做出回應。如果他們找不到可用到機會(或許因為他們相信不會有錯誤真的發生),他們可以忽略它,依靠 DBConn 的 destructor(析構函數)為他們調用 close。如果一個錯誤恰恰在那時發生——如果由 close 拋出——如果 DBConn 抑制了那個 exception(異常)或者終止了程序,他們將無處訴苦。畢竟,他們無處著手處理問題,他們將不再使用它。
Things to Remember
* destructor(析構函數)應該永不引發 exceptions(異常)。如果 destructor(析構函數)調用了可能拋出異常的函數,destructor(析構函數)應該捕捉所有異常,然后抑制它們或者終止程序。
* 如果 class(類)客戶需要能對一個操作拋出的 exceptions(異常)做出回應,則那個 class(類)應該提供一個常規的函數(也就是說,non-destructor(非析構函數))來完成這個操作。
- Preface(前言)
- Introduction(導言)
- Terminology(術語)
- Item 1: 將 C++ 視為 federation of languages(語言聯合體)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 確保 objects(對象)在使用前被初始化
- Item 5: 了解 C++ 為你偷偷地加上和調用了什么函數
- Item 6: 如果你不想使用 compiler-generated functions(編譯器生成函數),就明確拒絕
- Item 7: 在 polymorphic base classes(多態基類)中將 destructors(析構函數)聲明為 virtual(虛擬)
- Item 8: 防止因為 exceptions(異常)而離開 destructors(析構函數)
- Item 9: 絕不要在 construction(構造)或 destruction(析構)期間調用 virtual functions(虛擬函數)
- Item 10: 讓 assignment operators(賦值運算符)返回一個 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中處理 assignment to self(自賦值)
- Item 12: 拷貝一個對象的所有組成部分
- Item 13: 使用對象管理資源
- Item 14: 謹慎考慮資源管理類的拷貝行為
- Item 15: 在資源管理類中準備訪問裸資源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一個獨立的語句中將 new 出來的對象存入智能指針
- Item 18: 使接口易于正確使用,而難以錯誤使用
- Item 19: 視類設計為類型設計
- Item 20: 用 pass-by-reference-to-const(傳引用給 const)取代 pass-by-value(傳值)
- Item 21: 當你必須返回一個對象時不要試圖返回一個引用
- Item 22: 將數據成員聲明為 private
- Item 23: 用非成員非友元函數取代成員函數
- Item 24: 當類型轉換應該用于所有參數時,聲明為非成員函數
- Item 25: 考慮支持不拋異常的 swap
- Item 26: 只要有可能就推遲變量定義
- Item 27: 將強制轉型減到最少
- Item 28: 避免返回對象內部構件的“句柄”
- Item 29: 爭取異常安全(exception-safe)的代碼
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之間的編譯依賴
- Item 32: 確保 public inheritance 模擬 "is-a"
- Item 33: 避免覆蓋(hiding)“通過繼承得到的名字”
- Item 34: 區分 inheritance of interface(接口繼承)和 inheritance of implementation(實現繼承)
- Item 35: 考慮可選的 virtual functions(虛擬函數)的替代方法
- Item 36: 絕不要重定義一個 inherited non-virtual function(通過繼承得到的非虛擬函數)
- Item 37: 絕不要重定義一個函數的 inherited default parameter value(通過繼承得到的缺省參數值)
- Item 38: 通過 composition(復合)模擬 "has-a"(有一個)或 "is-implemented-in-terms-of"(是根據……實現的)
- Item 39: 謹慎使用 private inheritance(私有繼承)
- Item 40: 謹慎使用 multiple inheritance(多繼承)
- Item 41: 理解 implicit interfaces(隱式接口)和 compile-time polymorphism(編譯期多態)
- Item 42: 理解 typename 的兩個含義
- Item 43: 了解如何訪問 templatized base classes(模板化基類)中的名字
- Item 44: 從 templates(模板)中分離出 parameter-independent(參數無關)的代碼
- Item 45: 用 member function templates(成員函數模板) 接受 "all compatible types"(“所有兼容類型”)
- Item 46: 需要 type conversions(類型轉換)時在 templates(模板)內定義 non-member functions(非成員函數)
- Item 47: 為類型信息使用 traits classes(特征類)
- Item 48: 感受 template metaprogramming(模板元編程)
- Item 49: 了解 new-handler 的行為
- Item 50: 領會何時替換 new 和 delete 才有意義
- Item 51: 編寫 new 和 delete 時要遵守慣例
- Item 52: 如果編寫了 placement new,就要編寫 placement delete
- 附錄 A. 超越 Effective C++
- 附錄 B. 第二和第三版之間的 Item 映射