首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C++ 核心規範:析構函數絕不能讓異常逃逸

     編輯:關於C++

兩種呼叫析構函數的情境

  1. 正常銷毀: 物件離開作用域或被顯式 delete。
  2. 堆疊展開: 例外傳播過程中,例外處理機制銷毀沿途的區域物件。

在析構函數內部,無法區分 當前是哪種情境,也無法得知是否已有其他例外處於作用中。因此必須保守假設:析構函數執行時可能正處於堆疊展開。

為什麼異常不能離開析構函數?

若析構函數拋出例外,且此時已有另一個例外正在傳播(即雙重例外),C++ 標準規定立即呼叫 std::terminate() 終止程式。terminate 預設呼叫 abort(),不會釋放區域物件、不會執行任何清理,直接強制結束進程。這是災難性後果。

案例:Session 類別的日誌記錄

class Session {
public:
    ~Session() { logDestruction(this); } // 危險!
private:
    static void logDestruction(Session* addr);
};

若 logDestruction 拋出例外(如 I/O 錯誤),且此時正因其他錯誤展開堆疊,程式將瞬間終止。

正確做法:吞噬所有異常

Session::~Session() {
    try {
        logDestruction(this);
    } catch (...) { } // 捕獲並忽略所有例外
}
  • catch(...) 空主體不是無動作,它阻斷例外傳播出析構函數。
  • 即使 catch 內的記錄動作又拋出例外,也會導致 terminate,故必須確保 catch 區塊絕不拋出(必要時再套一層 try-catch 或使用不拋出生的 API)。

第二個理由:確保析構邏輯完整執行

若析構函數中途拋出例外,後續清理代碼(如釋放資源、提交交易)將不會執行。例如:

Session::~Session() {
    logDestruction(this); // 若拋出
    endTransaction();     // 此行永不執行 → 交易懸掛
}

即使調整順序,若 endTransaction 也可能拋出,仍需 try-catch 保護。

總結:鐵律與最佳實踐

  1. 析構函數必須捕獲所有內部例外,不讓任何例外逃出。
  2. catch 區塊應盡量為空,或僅做不拋出的記錄(如寫入預留緩衝區)。
  3. 若介面函數(如回調、虛擬函數)可能拋出,在析構函數呼叫時務必包裹 try-catch。
  4. 這也是為什麼標準庫容器的析構函數被標記 noexcept 的原因。

Herb Sutter 指出:「拋出例外的析構函數是邪惡的」。遵循此規則,是寫出健壯 C++ 程式的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved