构造函数抛异常时,已成功构造的成员按声明逆序自动析构,自身析构函数不执行;裸指针和手动资源不会被清理,须用raii保障异常安全。

构造函数抛异常后,已成功构造的成员对象会被自动析构,但当前对象自身的析构函数不会执行。
已构造成员的析构由编译器保证
只要某个成员(或基类)的构造函数完整执行完毕、没抛异常,它就算“已构造”。一旦外围构造函数因异常中止,C++ 标准强制要求:按初始化顺序的逆序,依次调用这些已构造成员的析构函数。这个过程由编译器生成的栈展开逻辑完成,无需手动干预。
-
B是A的成员,B构造成功 → 异常发生后,B::~B()一定被调用 - 多个成员按声明顺序初始化,析构则反向:若
A有成员m1、m2、m3,且前两个构造成功,则只调m2::~m2()再m1::~m1() - 基类同理:虚基类 → 直接基类 → 成员 → 当前类体,析构顺序完全逆序
原始指针和裸资源不会被自动清理
编译器只管“已构造对象”的析构,不管“你手动 new 出来的内存”或“open 得到的文件描述符”。如果在构造函数体里写了 resource = new int[100] 然后抛异常,这块内存就泄漏了——因为 resource 是 int* 类型,不是对象,不触发析构逻辑。
- 错误写法:
char* buf; buf = new char[4096]; throw std::runtime_error("fail");→ 内存泄漏 - 正确做法:改用
std::unique_ptr<char></char>或std::vector<char></char>,它们是对象,会自动析构并释放底层资源 - 初始化列表中抛异常(如
B b{arg}失败),不影响后续成员,但已成功的前面成员仍会析构
智能指针和容器成员的构造失败行为
当容器或智能指针自身是成员,而其模板参数类型 T 的构造失败时,该智能指针/容器对象本身可能根本没构造出来,也就谈不上析构;但若它已构造成功(比如 std::shared_ptr<t></t> 的控制块建好了),而 T 的构造抛异常,则控制块会自动销毁,T 的未完成部分不产生副作用。
-
std::shared_ptr<b> sp{new B()}</b>中B()抛异常 →sp构造失败,控制块和B对象都未完成,无泄漏 - 但
std::shared_ptr<b> sp;</b>+sp.reset(new B())在函数体内抛异常 → 必须靠 RAII 封装,否则new B()分配的内存会泄漏 -
std::vector<:string> v(1000)</:string>中第 500 个std::string构造失败 → 前 499 个自动析构,v对象本身不存活,不调v.~vector()
真正容易被忽略的是:析构顺序严格依赖初始化顺序,而初始化顺序由成员声明顺序决定,不是初始化列表中的书写顺序。一旦混用声明顺序和初始化列表顺序,就可能误判哪些成员已被构造、哪些还没开始——这直接影响异常路径下的资源清理可靠性。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











