构造函数抛异常时裸指针资源必然泄漏,因对象未完成构造导致析构函数不调用,且未初始化成员不会被清理;必须用智能指针在初始化列表中接管资源,并避免在构造函数中执行I/O等易失败操作。

构造函数抛异常时,已分配的裸指针资源必然泄漏
这是 C++ 中一个经典陷阱:只要在构造函数体中用 new 分配了内存,又没来得及交给 RAII 对象管理,一旦后续抛出异常,这部分内存就永远丢失了。因为对象未构造完成,~B() 不会被调用,而成员变量的析构只按初始化顺序逆序执行——靠前声明、已成功构造的成员能被清理;靠后声明、尚未初始化的成员(比如裸指针)根本不会触发析构逻辑。
必须把资源交给 RAII 对象管理,且在初始化列表中完成
不能等构造函数体里再 new 再赋值给裸指针;也不能在初始化列表里写 resource(new char[100]) 这种裸分配。正确做法是直接用 std::unique_ptr 或 std::shared_ptr 等智能指针接管:
-
std::unique_ptr<char> resource{new char[100]}</char>必须出现在初始化列表中,而不是构造函数体内 - 如果资源获取可能失败(如打开文件、加载图像),应封装成独立的 RAII 类型(例如
ImageLoader),并在初始化列表中构造它 - 避免在初始化列表中调用可能抛异常的自由函数;若必须,先确保参数校验已在工厂函数中完成
不要在构造函数里做 I/O 或重操作
构造函数职责是“建立对象的有效初始状态”,不是“执行业务逻辑”。像读文件、解码音频、网络请求这类操作,失败概率高、耗时长、易抛异常,统统不该放在构造函数里:
立即学习“C++免费学习笔记(深入)”;
- 常见错误:
theImage(std::make_unique<image>(imageFileName))</image>—— 如果Image构造函数内部做了文件读取并抛异常,那theImage成员本身已析构(因为它是std::unique_ptr,其析构不依赖外部资源),但整个对象仍算未完成构造,其他成员(比如更早声明的std::string)虽被析构,但你无法控制它们是否“安全”释放 - 更稳妥做法:把
Image改为延迟加载,构造函数只存文件名;真正加载推迟到首次访问时,由用户显式处理异常 - 或者用工厂函数:
static std::optional<bookentry> create(...)</bookentry>,内部用try/catch封装全部可能失败步骤,失败则返回std::nullopt
const 成员或引用成员导致初始化列表无法绕过?用委托构造 + 工厂模式
当类有 const 成员或引用成员时,必须在初始化列表中赋值,但又担心该值的构造可能抛异常(比如 const std::string s{loadConfig()}),这时不能把异常处理逻辑塞进初始化列表:
- 把易失败的初始化提取到静态辅助函数中,该函数返回
std::expected<t error></t>或std::optional<t></t>,并在工厂函数中统一处理 - 使用委托构造函数:主构造函数只接受已验证/已构造好的参数,把校验和资源获取提前到调用方
- 避免在初始化列表中调用非 trivial 的自定义类型构造函数;优先让这些类型自身也遵守 RAII 原则
最常被忽略的一点是:你以为把 delete 写在 catch 里就安全了,但一旦成员中有 const 或引用,或初始化顺序与析构顺序不匹配,手动 delete 很可能删错对象、重复释放,或根本来不及执行。唯一可靠路径,是让每个资源从诞生起就被某个栈上对象生命周期绑定。


















