條款08 別讓異常逃離解構函式,條款08逃離函數
結論:
1. 解構函式絕對不要吐出異常。如果一個被解構函式調用的函數可能拋出異常,解構函式應該能夠捕捉任何異常,然後吞下他們(不傳播)或結束程式。
2. 如果客戶需要對某個操作函數運行期間拋出的異常做出反應,那麼class應該提供一個普通函數(而不是在解構函式中)執行該操作。
C++ 不禁止但不鼓勵從解構函式引發異常。考慮:
class Widget {public: ... ~Widget() { ... } // 假設這裡可能吐出一個異常};void doSomething(){ std::vector<Widget> v; ...} // v在這裡被自動銷毀 當 vector v 被析構時,它有責任析構它包含的所有 Widgets。但假設在那些調用期間,先後有兩個Widgets拋出異常,對於 C++ 來說,這太多了。在兩個異常同時存在的情況下,程式若不是結束執行就是導致不明確行為。在本例中將導致不明確行為,使用標準庫的任何其他容器(如list,set)或TR1的任何容器甚至array,也會出現相同情況。C++ 不喜歡解構函式吐出異常。
如果你的解構函式需要執行一個可能失敗而拋出一個異常的操作,該怎麼辦呢?假設使用一個class負責資料庫連接,為了確保客戶不會忘記在 DBconnection對象上調用 close(),一個合理的想法是建立一個用來管理DBConnection資源的類,並在其解構函式中調用close:
class DBConn { // 這個類用來管理DBConnection對象public: // objects ... ~DBConn() // 確保資料庫連接總是會被關閉 { db.close();}private: DBConnection db;};它允許客戶像這樣編程:
{ // 開啟一個區塊(block) DBConn dbc(DBConnection::create()); // 建立DBConnection並交給DBConn對象以便管理 ... // 通過DBConn的介面使用DBConnection對象} //在區塊結束點,DBConn對象被銷毀,因而自動為DBConnection對象調用close 只要調用 close 成功,一切都美好。但是如果這個調用導致一個異常,DBConn 的解構函式將傳播那個異常,也就是允許它離開解構函式。這就產生了問題,因為解構函式拋出了一個燙手的山芋。
有兩個主要的方法避免這個麻煩:
(1)Terminatethe program:如果 close 拋出異常就終止程式,一般是通過調用 abort。
DBConn::~DBConn(){try { db.close(); } catch (...) { 製作運轉記錄,記下對close的調用失敗; std::abort(); }} 它有一個好處是:阻止異常從解構函式中傳播出去(那會導致不明確的行為)。也就是說,調用 abort 可以預先制“不明確行為”於死地。
(2)Swallowthe exception:吞下因調用close而發生的異常。在此例中將在第一種方法下去掉abort那句語句。
通常,將異常吞掉是個壞主意,因為它隱瞞了“某些動作失敗”的重要訊息!然而,有些時候,吞下異常比冒程式過早終止或不明確行為的風險更可取。程式必須能夠在遭遇到一個錯誤並忽略之後還能繼續可靠地運行,這才能成為一個可行的選擇。
以上方法的問題都在於兩者無法對引起 close 拋出異常的情況做出回應。
一個更好的策略是重新設計 DBConn 的介面,以使客戶有機會對可能發生的問題做出回應。
class DBConn {public: ... void close() // 供客戶使用的新函數 { db.close(); closed = true; } ~DBConn() { if (!closed) { try { db.close(); // 關閉串連(如果客戶不那麼做的話) } catch (...) { // 如果關閉動作失敗,記錄下來並結束程式或吞下異常 製作運轉記錄,記下對close的調用失敗; ... } } } private: DBConnection db; bool closed;}; 這樣把調用 close 的責任從 DBConn 的解構函式移交給 DBConn 的客戶(同時在 DBConn 的解構函式中仍內含一個“雙保險調用”)。如果某個操作可能在失敗時拋出異常,而又存在某種需要必須處理該異常,那麼這個異常必須來自解構函式以外的某個函數。這是因為解構函式)引發異常是危險的,永遠都要冒著程式過早終止或 不明確行為的風險。在本例中,讓客戶自己調用 close 並不是強加給他們的負擔,而是給他們一個處理錯誤的機會。他們可以忽略它,依靠 DBConn 的解構函式去調用 close。如果真有錯誤發生,close的確拋出異常而且DBConn吞下該異常或結束程式,客戶沒有立場抱怨,畢竟他們曾有機會第一手處理問題,而他們選擇了放棄。