C++箴言:防止異常離開析構函數

C++ 並不禁止從析構函數中引發異常,但是這確實妨礙了實踐。至于有什麽好的理由,考慮:

class Widget {

public:

...

~Widget() { ... } // assume this might emit an exception

};

void doSomething()

{

std::vector v;

...

} // v is automatically destroyed here

當 vector v 被析構時,它有責任銷毀它包含的所有 Widgets。假設 v 中有十個 Widgets,在銷毀第一個的時候,抛出一個異常。其他 9個 Widgets 仍然必須被銷毀(否則他們持有的任何資源將被泄漏),所以 v 應該調用它們的析構函數。但是假設在這個調用期間,第二個 Widgets 的析構函數又抛出一個異常。現在有兩個異常同時在活動中,對于 C++ 來說這太多了。在非常巧合的條件下發生這樣兩個同時活動的異常,程序的執行會終止或者引發未定義行爲。在本例中,將引發未定義行爲。與此相同,使用任何標准庫容器(比如,list,set),任何 TR1中的容器,甚至是一個數組,都可能會引發未定義問題。並非必須是容器或數組才會陷入麻煩。程序夭折或未定義行爲是析構函數引發異常的結果,即使沒有使用容器或數組也會如此。C++ 不喜歡引發異常的析構函數。 這比較輕易理解,但是假如你的析構函數需要執行一個可能失敗而抛出異常的操作,該怎麽辦呢?例如,假設你與一個數據庫連接類一起工作:

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

C++箴言:防止異常離開析構函數
更多內容請看C/C++技術專題專題,或

爲了確保客戶不會忘記調用 DBconnection 對象的 close,一個合理的主意是爲 DBConnection 建立一個資源治理類,在它的析構函數中調用 close。這樣的資源治理類將在以後的文章中探討,但在這裏,只要認爲這樣一個類的析構函數看起來像這樣就足夠了:

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 那就好了,但是假如這個調用導致了異常,DBConn 的析構函數將散播那個異常,也就是說,它將離開析構函數。這就産生了問題,因爲析構函數抛出了一個燙手的山芋。

C++箴言:防止異常離開析構函數
更多內容請看C/C++技術專題專題,或

有兩個主要的方法避免這個麻煩。DBConn 的析構函數能:

終止程序 假如 close 抛出異常,調用 abort。

DBConn::~DBConn()

{

try { db.close(); }

catch (...) {

make log entry that the call to close failed;

std::abort();

}

}

假如程序在析構過程遭碰到錯誤後不能繼續運行,這就是一個合理的選擇。它有一個好處是:假如答應從析構函數散播異常可能會引起未定義行爲,這樣就能防止它發生。也就是說,調用 abort 就預先防止了未定義行爲。

抑制這個異常 起因于調用 close:

DBConn::~DBConn()

{

try { db.close(); }

catch (...) {

make log entry that the call to close failed;

}

}

通常,抑制異常是一個不好的主意,因爲它會隱瞞重要的信息——某些事情失敗了!可是,有些時候,抑制異常比冒程序夭折或未定義行爲的風險更可取。程序必須能夠在遭碰到錯誤並忽略之後還能繼續可靠地執行,這才能成爲一個可行的選擇。

這些方法都不太吸引人。它們的問題在于程序無法在第一現場對引起 close 抛出異常的條件做出回應。

一個更好的策略是設計 DBConn 的接口,以使它的客戶有機會對可能會發生的問題做出回應。例如,DBConn 能夠自己提供一個 close 函數,從而給客戶一個機會去處理從那個操作中發出的異常。它還能保持對它的 DBConnection 是否已被關閉的跟蹤,假如沒有關閉就在析構函數中自己關閉它。這樣可以防止連接被泄漏。假如在 DBConnection 的析構函數中調用 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 的析構函數轉移到 DBConn 的客戶(同時在 DBConn 的析構函數中包含一個“候補”調用)可能會作爲一種肆無忌憚地推卸責任的做法而刺激你。你甚至可以把它看作一個忠告(使接口易于正確使用)的違反。實際上,這都不正確。假如一個操作可能失敗而抛出一個異常,而且可能是一個需要處理的異常,這個異常就必須來自非析構函數。這是因爲析構函數引發異常是危險的,永遠都要冒著程序夭折或未定義行爲的風險。在此例中,讓客戶調用 close 並不是強加給他們的負擔,而是給他們一個時機去應付錯誤,否則他們將沒有機會做出回應。假如他們找不到可用到機會(或許因爲他們相信不會有錯誤真的發生),他們可能忽略它,依靠 DBConn 的析構函數爲他們調用 close。假如一個錯誤恰恰發生在那時——假如由 close 抛出——假如 DBConn 抑制了那個異常或者終止了程序,他們將無處訴苦。究竟,他們無處著手處理問題,他們將不再使用它。

Things to Remember

·析構函數應該永不引發異常。假如析構函數調用了可能抛出異常的函數,析構函數應該捕捉任何異常,然後抑制它們或者終止程序。

·假如類客戶需要能對一個操作抛出的異常做出回應,則那個類應該提供一個常規的(非析構函數)函數來完成這個操作。

C++箴言:防止異常離開析構函數
更多內容請看C/C++技術專題專題,或

翻譯:Effective C++, 3rd Edition, Item 8: 防止因爲異常而離開析構函數
Item 8: 防止因爲異常而離開析構函數C++ 並不禁止從析構函數中引發異常,但是這確實妨礙了實踐。至于有什麽好的理由,考慮:class Widget {public: ... ~Widget() { ... } // assume this might emit a...查看完整版>>翻譯:Effective C++, 3rd Edition, Item 8: 防止因爲異常而離開析構函數
 
C++箴言:用非成員非友元函數取代成員函數
  想象一個象征 web 浏覽器的類。在大量的函數中,這樣一個類也許會提供清空已下載成分的緩存。清空已訪問 URLs 的曆史,以及從系統移除所有 cookies 的功能: class WebBrowser { public:  ...  void clearC...查看完整版>>C++箴言:用非成員非友元函數取代成員函數
 
C++箴言:避免析構函數調用虛函數
  假如你已經從另外一種語言如C#或者java轉向了C++,你會覺得,避免在類的構造函數或者析構函數中調用虛函數這一原則有點違反直覺。但是在C++中,違反這個原則會給你帶來難以預料的後果和無盡的煩惱。   正文 ...查看完整版>>C++箴言:避免析構函數調用虛函數
 
翻譯:Effective C++, 3rd Edition, Chapter 2. Constructors(構造函數),Destructors(析構函數)與 Assignment Operators(賦值運算
Chapter 2. Constructors(構造函數),Destructors(析構函數)與 Assignment Operators(賦值運算符)作者:譯者:發布:幾乎每一個你自己寫的 class(類)都會有一個或多個 constructors(構造函數),一個 destr...查看完整版>>翻譯:Effective C++, 3rd Edition, Chapter 2. Constructors(構造函數),Destructors(析構函數)與 Assignment Operators(賦值運算
 
C++ FAQ Lite[11]--析構函數(新)
析構函數(Part of )簡體中文版翻譯:)FAQs in section : [11.1] 析構函數做什麽?析構函數爲對象舉行葬禮。 析構函數用來釋放對象所分配的資源。舉例來說,Lock 類可能鎖定了一個信號量,那麽析構函數...查看完整版>>C++ FAQ Lite[11]--析構函數(新)
 
C++箴言:考慮支持不抛異常的swap
很多企業在發展過程中,都有過幾套治理軟件共用的經曆,往往造成在企業內部形成信息孤島,無法統一治理的窘境 中冀集...查看完整版>>C++箴言:考慮支持不抛異常的swap
 
C++箴言:聲明爲非成員函數時機
  我談到讓一個類支持隱式類型轉換通常是一個不好的主意。當然,這條規則有一些例外,最普通的一種就是在創建數值類型時。例如,假如你設計一個用來表現有理數的類,答應從整數到有理數的隱式轉換看上去並非不合理...查看完整版>>C++箴言:聲明爲非成員函數時機
 
C++箴言:絕不重定義繼承的非虛擬函數
作者: fatalerror99       出處:BLOG   假設我告訴你 class(類)D 從 class(類)B publicly derived(公有繼承),而且在 class(類)B 中定義了一個 public member func...查看完整版>>C++箴言:絕不重定義繼承的非虛擬函數
 
C++箴言:考慮可選的虛擬函數的替代方法
  現在你工作在一個視頻遊戲上,你在遊戲中爲角色設計了一個 hierarchy(繼續體系)。你的遊戲中有著變化多端的惡劣環境,角色被傷害或者其它的健康狀態降低的情況並不罕見。因此你決定提供一個 member function(...查看完整版>>C++箴言:考慮可選的虛擬函數的替代方法
 
 
回到王朝網路移動版首頁