構造函數拋出異常時,**已構造完成的成員會自動析構**,但**構造過程中手動分配的資源(`new`、檔案句柄、鎖)不會自動釋放**,導致洩漏。本文詳細說明機制、錯誤示範、RAII 解決方案與 `try-catch` 重拋模式。
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 記憶體洩漏
#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 自動清理
};
優勢:零額外代碼、無例外安全漏洞、語義明確(獨佔所有權)。
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");
}
};
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 類型),異常安全自動保證。
「在構造函數中阻止資源洩漏:用智慧指針管理動態資源,或用 `try-catch` 手動清理並重拋。」