为何C++合成的拷贝控制操作默认均为noexcept?
合成特殊成员函数的异常规范本质
你测试得到的全为1的输出,是C++标准明确规定的推导结果,而非编译器的自定义实现特性。
- 编译器为类合成的各类特殊成员函数(默认构造、拷贝构造、移动构造、拷贝赋值、移动赋值、析构),不存在固定不变的noexcept属性。它的异常规范完全是动态推导得出的:合成函数的异常安全性,和它实际执行的所有子操作的异常安全性完全一致。
- 推导逻辑非常直白:合成的拷贝控制操作,本质上就是依次调用所有基类子对象、所有非静态数据成员对应的构造、拷贝、移动、析构操作。只要这些被调用的子操作全部是不抛出异常的(即被标记为
noexcept(true)),最终合成出来的类级别的操作,就会被自动标记为noexcept(true);只要任意一个子操作可能抛出异常,合成出的函数就会被标记为可能抛出异常。 - 你测试用的
Foo是空结构体,没有任何非静态数据成员,也没有继承任何基类,所有合成的特殊成员函数本质上都是无实际操作的平凡实现,不存在任何可能抛出异常的执行路径,所以全部检测结果为1是完全符合规则的。
为什么标准要设计这种自动推导noexcept的规则
- 这个设计是和C++11引入的移动语义深度绑定的核心配套:标准库很多关键组件(比如
std::vector的内存扩容逻辑)会通过noexcept判断,决定资源重分配时是选择移动语义还是退化为更安全的拷贝语义——如果一个类型的移动操作被判定为可能抛异常,容器扩容时就不会使用移动,避免移动中途抛异常导致元素状态不一致、资源泄漏。 - 如果标准不对编译器合成的特殊成员函数自动推导noexcept,那么大量由无异常操作组成的普通类,都会因为没有手动给特殊成员函数加
noexcept标记,错过这些关键的性能优化,相当于把移动语义的大部分优化收益给废掉了。 - 从逻辑一致性上看:如果组成类的所有成员、所有基类的对应操作都不可能抛异常,那编译器合成的整体操作本身就根本没有抛出异常的可能,把它的异常规范写为“可能抛异常”是完全没有依据的,既会误导静态异常检查逻辑,也会让依赖异常规范的优化全部失效。
补充个反例帮你验证推导规则:
如果给之前的Foo类加一个自定义成员,且这个成员的拷贝构造会抛出异常,那对应的合成操作的noexcept结果就会变为false。比如:struct ThrowingCopy { ThrowingCopy() = default; ThrowingCopy(const ThrowingCopy&) { throw 1; } }; struct NotNoexceptFoo { ThrowingCopy mem; // 该成员的拷贝构造可抛 }; // 此时 noexcept(NotNoexceptFoo{}.NotNoexceptFoo(NotNoexceptFoo{})) 的结果为 false
内容的提问来源于stack exchange,提问作者Maestro
相关产品推荐
相关产品推荐

