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

Effective C++ 條款 08:絕不讓異常逃離析構函數

     編輯:C++ 入門

核心觀點

析構函數絕不能拋出異常。若析構函數拋出異常,程序將面臨「過早終止」或「未定義行為」的風險。這是 C++ 與 Java/C# 等語言的重大差異之一。

問題場景

假設有一個數據庫連接類 DBConnection,其 close() 方法在失敗時會拋出異常:

class DBConnection {
public:
    static DBConnection create();
    void close(); // 失敗時拋出異常
};

推薦解決方案:RAII 包裝器 + 異常吞噬/記錄

創建管理類 DBConn,提供顯式 close() 供調用者主動處理異常,析構函數中兜底調用並捕獲所有異常:

class DBConn {
public:
    DBConn() : db(DBConnection::create()), closed(false) {}
    ~DBConn() {
        if (!closed) {
            try {
                db.close(); // 關閉連接
            } catch (...) {
                // 方案一:記錄日誌並終止程序
                log_error("DBConnection close failed in destructor");
                std::abort();
                
                // 方案二:吞噬異常(僅記錄,不傳播)
                // log_error("DBConnection close failed, exception swallowed");
            }
        }
    }
    
    void close() { // 供客戶顯式調用
        db.close();
        closed = true;
    }

private:
    DBConnection db;
    bool closed;
};

設計原則

  1. 提供顯式釋放介面:讓調用者有機會在正常控制流中處理異常(如重試、記錄、回滾)
  2. 析構函數作為安全網:防止資源洩漏,但決不向外拋出異常
  3. 記錄而非忽略:即使選擇吞噬異常,也必須記錄日誌以便事後排查

反面教材

// 錯誤示範:析構函數直接拋出異常
class BadDBConn {
public:
    ~BadDBConn() {
        db.close(); // 若拋出異常且正在堆疊展開期間,程序直接 terminate
    }
private:
    DBConnection db;
};

深度解析:為什麼這麼危險?

  • 雙重異常導致 terminate:若在堆疊展開(處理另一異常)期間,析構函數又拋出新異常,C++ 執行環境會直接調用 std::terminate()
  • 容器元素析構失敗:標準庫容器銷毀元素時若遇到異常拋出,行為未定義
  • 資源洩漏連鎖反應:析構函數中途拋出導致後續成員變量析構函數不被調用

最佳實踐檢查清單

☐ 所有析構函數標記 noexcept(C++11 起默認)
☐ 析構函數內部調用的可能拋出異常的函數均用 try-catch 包裹
☐ 提供顯式 close()/release() 供調用者主動釋放資源
☐ 異常被捕獲後至少記錄關鍵上下文信息

總結

「析構函數不拋出異常」不是建議,是鐵律。通過 RAII 包裝器提供顯式釋放介面,在析構函數中兜底捕獲並記錄/終止,是處理可能失敗的清理操作的標準模式。

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