构造函数中new失败会抛std::bad_alloc,此时已构造的成员按逆序析构,对象内存自动释放,但未完成构造的部分不析构;应使用RAII(如unique_ptr)而非手动清理。

构造函数里 new 失败会怎样
直接抛 std::bad_alloc,这是 C++ 标准行为。但关键不在“抛不抛”,而在“抛了之后对象有没有被部分构造、析构函数会不会被调用”。答案是:不会调用析构函数——因为对象根本没构造完成,this 指针无效,连虚表都可能没初始化完。
常见错误是手动 delete 已分配的资源再 throw,比如:
MyClass() {
ptr1 = new int[100];
ptr2 = new char[200]; // 这里失败
delete[] ptr1; // 手动清理?错!容易漏、易重复、不异常安全
throw std::runtime_error("oops");
}
正确做法是把资源交给 RAII 对象管理:
- 用
std::unique_ptr替代裸指针,分配成功自动接管,失败时已构造的unique_ptr会正常析构(释放其持有的资源) - 成员变量按声明顺序构造,也按逆序析构;所以把依赖资源的成员放在靠后的声明位置,能保证前面资源先析构
- 避免在构造函数里做复杂 I/O 或网络操作——这些失败不可控,且难以回滚
构造函数抛异常后,对象内存是否泄漏
不会泄漏。C++ 标准保证:如果构造函数抛出异常,编译器会自动调用已完成构造的成员对象的析构函数,并释放该对象占用的内存(即调用 operator delete)。注意:这只是栈上或 new 分配的原始内存,不是你手动 malloc 或 VirtualAlloc 的。
立即学习“C++免费学习笔记(深入)”;
但有例外:
- 如果你重载了类的
operator new,且在其中做了额外资源分配(比如注册句柄),而没配套实现异常安全的operator delete,那这部分资源就真漏了 - 使用
placement new时,编译器不负责调用operator delete,必须手动匹配调用operator delete(哪怕构造失败) -
std::vector等容器在扩容时内部调用new失败,也会抛异常,但它自己已确保无泄漏——前提是你的元素类型构造函数也是异常安全的
想“静默失败”返回空对象?别这么干
C++ 构造函数没有返回值,无法返回 nullptr 或 std::nullopt。试图用“构造函数设标志位 + 提供 valid() 成员”是反模式:对象逻辑上已存在,但处于无效状态,后续任何成员函数都得加运行时检查,极易遗漏。
更安全的替代方案:
- 用工厂函数返回
std::optional(C++17)或std::unique_ptr,把构造逻辑和成败判断收口到一处 - 对资源敏感类型(如文件句柄、socket),优先用
std::expected(C++23)或第三方outcome库,显式表达可能失败 - 禁止默认构造,强制用户走工厂路径,从源头杜绝“半成品对象”被创建
继承体系中基类构造失败的影响
如果派生类构造函数开始执行,但基类构造函数抛异常,则派生类的成员**一个都不会构造**,也不会调用派生类的析构函数。整个对象生命周期止步于基类。
这意味着:
- 基类构造函数应尽量轻量,避免在其中申请多个资源;否则失败时,派生类完全不知道发生了什么
- 不要在基类构造函数里调用虚函数——此时虚表还没就绪,实际调用的是基类版本(即使你写了
virtual) - 若需复杂初始化,把逻辑拆进一个
init()成员函数,并明确文档说明“必须在构造后立即调用”,但这本质上放弃了异常安全保证
最棘手的其实是多继承:各基类按声明顺序构造,任一失败都会导致后续基类和所有成员跳过构造。调试时看到“某个成员的构造函数根本没进”,先查它前面的基类或成员是否抛了异常。










