构造器异常安全的核心目标是确保对象创建失败时不留下半初始化状态、不泄漏资源、不破坏程序一致性;需用RAII封装每份资源,避免裸操作,构造函数中忌多步资源申请,析构函数必须noexcept,可通过模拟异常测试验证强异常安全性。

构造器异常安全的核心目标是:一旦对象创建失败,绝不留下半初始化状态、不泄漏资源、不破坏程序整体一致性。这在动态内存、文件句柄、锁、网络连接等场景中尤为关键。
用RAII封装每一份资源
不要在构造函数里直接调用 new 或 fopen 后裸存指针或句柄。每个资源必须由专属的RAII类管理,比如:
- std::unique_ptr<int> 替代 int* —— 构造失败时,已构造成功的 unique_ptr 会自动析构并释放内存;
- std::fstream 替代 FILE* —— 打开失败抛异常,对象未完全构造,文件句柄不会泄露;
- std::lock_guard 替代手动 mutex.lock()/unlock() —— 即使后续语句抛异常,锁也会在作用域结束时自动释放。
RAII的本质是“资源生命周期 = 对象生命周期”。只要资源被包装进栈上对象,C++保证:构造成功则资源就绪,构造中途失败则已构造部分自动逆向析构。
避免构造函数中做多步资源申请
如果一个构造函数要同时分配内存、打开文件、加锁,其中任一环节失败,前面已成功的资源就可能泄漏(因为C++不为部分构造的对象调用析构函数)。
更稳妥的做法是:
- 把复杂初始化拆成两阶段:先完成所有可能失败的操作(如 new、fopen),再用 noexcept 的 move 或 swap 组装对象;
- 或使用“工厂函数 + 私有构造”,让资源获取与对象组装分离,失败时直接返回空或抛异常,不产生半成品对象。
禁止在析构函数中抛异常
析构函数必须是 noexcept 的(显式声明或默认满足)。否则,当栈展开过程中另一个异常正在传播时,析构函数再抛异常,程序会立即调用 std::terminate 终止。
这意味着:
- 关闭文件、释放内存等操作即使出错,也应吞掉错误或记录日志,绝不能 throw;
- 若某资源释放本身可能失败(如 socket shutdown 失败),应改用 RAII 包装器内部静默处理,而非暴露给用户析构逻辑。
验证强异常安全性的简单方法
对关键类,可模拟构造中途失败来检验:
- 临时重载全局 operator new,在第 N 次调用时抛 std::bad_alloc;
- 用 std::set_new_handler 注入可控异常;
- 运行后检查:是否有内存未释放?文件描述符是否增长?互斥锁是否仍被持有?
通过这类测试,能快速暴露未被 RAII 覆盖的裸资源操作。

















