| 導購 | 订阅 | 在线投稿
分享
 
 
 

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

2008-06-01 01:58:12  編輯來源:互聯網  简体版  手機版  移動版  評論  字體: ||

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++技術專題專題,或

  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    [url=/bbs/detail_1785103.html][img]http://image.wangchao.net.cn/it/1323424700331.gif[/img][/url] 更多內容請看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 的析構函數將散播那個異常,也就是說,它將離開析構函數。這就産生了問題,因爲析構函數抛出了一個燙手的山芋。 [url=/bbs/detail_1785103.html][img]http://image.wangchao.net.cn/it/1323424700374.gif[/img][/url] 更多內容請看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   ·析構函數應該永不引發異常。假如析構函數調用了可能抛出異常的函數,析構函數應該捕捉任何異常,然後抑制它們或者終止程序。   ·假如類客戶需要能對一個操作抛出的異常做出回應,則那個類應該提供一個常規的(非析構函數)函數來完成這個操作。 [url=/bbs/detail_1785103.html][img]http://image.wangchao.net.cn/it/1323424700391.gif[/img][/url] 更多內容請看C/C++技術專題專題,或
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 熱帖排行
 
王朝網路微信公眾號
微信掃碼關註本站公眾號 wangchaonetcn
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有