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

C++ 構造函數異常安全:資源洩漏防禦與 RAII 實戰

     編輯:關於C語言

前言

構造函數拋出異常時,**已構造完成的成員會自動析構**,但**構造過程中手動分配的資源(`new`、檔案句柄、鎖)不會自動釋放**,導致洩漏。本文詳細說明機制、錯誤示範、RAII 解決方案與 `try-catch` 重拋模式。

核心機制:C++ 標準保證

  1. 構造函數體 `{}` 開始執行前,**所有成員已構造完成**(透過初始化列表或預設構造)。
  2. 若構造函數拋出異常,**已構造的成員按逆序自動析構**。
  3. **物件本身不存在**,因此**析構函數不會被調用**。
  4. 若用 `new` 創建物件且構造拋異常,**記憶體會被 `operator delete` 釋放**(編譯器插入清理碼)。

錯誤示範:裸指針成員洩漏


class Foo {
    A* pA; B* pB; C* pC;
public:
    Foo() {
        pA = new A();  // 成功
        pB = new B();  // 成功
        pC = new C();  // 💥 拋出異常
    }
    ~Foo() {
        delete pA; delete pB; delete pC; // 永遠不會執行!
    }
};
// 結果:pA、pB 記憶體洩漏

方案一:RAII 智慧指針(現代 C++ 首選)


#include <memory>

class Foo {
    std::unique_ptr<A> pA;
    std::unique_ptr<B> pB;
    std::unique_ptr<C> pC;
public:
    Foo() : pA(std::make_unique<A>()),
            pB(std::make_unique<B>()),
            pC(std::make_unique<C>()) {}  // 任一失敗,前者自動釋放
    // 無需析構函數,unique_ptr 自動清理
};

優勢:零額外代碼、無例外安全漏洞、語義明確(獨佔所有權)。

方案二:標準容器/字串/RAII 封裝


class Bar {
    std::vector<int> bigBuffer;  // 自動管理記憶體
    std::fstream logFile;         // 自動關閉檔案
    std::mutex mtx;               // 無需手動 lock/unlock
public:
    Bar() : bigBuffer(1000000), logFile("log.txt") {
        if (!logFile) throw std::runtime_error("open failed");
    }
};

方案三:`try-catch` 重拋模式(C++98/無智慧指針環境)


class LegacyFoo {
    A* pA; B* pB; C* pC;
public:
    LegacyFoo() {
        try {
            pA = new A();
            pB = new B();
            pC = new C();  // 可能拋出
        } catch (...) {
            delete pA;  // 刪除空指針安全
            delete pB;
            delete pC;  // 可能為 nullptr
            throw;      // 重拋,讓上層知曉失敗
        }
    }
    ~LegacyFoo() { delete pA; delete pB; delete pC; }
};

關鍵點**:`delete nullptr` 安全;`throw;` 保留原始異常類型與堆疊追蹤。

初始化列表異常處理


class Baz : public Base {
    std::unique_ptr<Resource> res;
public:
    Baz() : Base(), res(std::make_unique<Resource>()) {
        // 若 Base 構造拋異常,res 尚未構造,無需清理
        // 若 res 構造拋異常,Base 已構造完成 → Base 析構自動調用
    }
};

**結論**:盡可能在初始化列表完成資源獲取(RAII 類型),異常安全自動保證。

Effective C++ 條款 10 回顧

「在構造函數中阻止資源洩漏:用智慧指針管理動態資源,或用 `try-catch` 手動清理並重拋。」

檢查清單

  • ✅ 所有動態分配資源是否由 RAII 類型持有?(`unique_ptr`, `shared_ptr`, `vector`, `string`, `fstream`, `lock_guard`)
  • ✅ 是否避免在構造函數體中直接 `new`/`malloc`/`fopen`?
  • ✅ 若必須手動管理,是否有 `try-catch` 清理並 `throw;`?
  • ✅ 基類析構函數是否 `virtual`?(多態刪除安全)
  • ✅ 成員宣告順序是否與初始化列表順序一致?(避免使用未初始化成員)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved