noexcept 是函数不抛异常的契约声明,违反则调用 std::terminate();需确保函数及所有调用链均不抛异常,移动操作未声明 noexcept 会导致 vector 扩容降级为拷贝。

noexcept 用在函数声明末尾,不是可选修饰而是契约声明
noexcept 表示该函数承诺不抛出任何异常;编译器会据此做优化(比如省略栈展开逻辑),标准库容器在移动操作中也会依赖它判断是否能无异常地转移资源。一旦函数体内抛出异常而声明了 noexcept,程序直接调用 std::terminate() —— 不是报错,是立即终止,没有栈回溯。
常见误用是给可能抛异常的函数硬加 noexcept,比如内部调用了 new、std::vector::push_back() 或用户自定义类型构造函数却没确认它们是否也 noexcept。
- 只对确定不会抛异常的函数加
noexcept:空函数、纯算术运算、delete已分配指针、移动赋值/构造(当成员都noexcept) - 用
noexcept(expression)条件声明更安全:例如void f() noexcept(noexcept(g())),表示“如果g()是noexcept,那f也是” - 析构函数默认隐式
noexcept,显式写出来只是强调意图,也便于模板推导
移动操作不加 noexcept 可能导致 vector 扩容时降级为拷贝
std::vector 在重新分配内存扩容时,若元素类型有 noexcept 移动构造函数,就直接移动;否则退回到拷贝构造 + 原地析构——性能差一倍,还可能因拷贝失败导致异常传播。
典型现象:你写了 MyClass(MyClass&&) noexcept,但忘了把成员变量的移动构造也设为 noexcept,结果整个类的移动构造仍被判定为可能抛异常。
立即学习“C++免费学习笔记(深入)”;
- 检查所有成员(含基类)的移动操作是否都声明了
noexcept - 用
static_assert(std::is_nothrow_move_constructible_v<myclass>)</myclass>在编译期验证 - 不要依赖编辑器自动补全:IDE 可能只加了
MyClass(MyClass&&),漏掉noexcept
noexcept 函数里调用可能抛异常的代码必须自己处理
声明了 noexcept 就不能让异常逃逸。但这不等于禁止使用可能抛异常的函数,而是得兜住:用 try/catch 捕获并转为返回码、日志或直接终止。
比如你想在 noexcept 函数里打开文件,std::ifstream 构造本身不抛异常(默认关闭 exceptions()),但如果你开了 ifs.exceptions(std::ios_base::failbit),那就不能再在 noexcept 函数里用它。
- 优先选不抛异常的底层接口:比如用
fopen而非std::ifstream(后者默认不抛,但容易被误配) - 若必须调用可能抛异常的函数,用
try { ... } catch (...) { std::abort(); }或记录错误后返回错误码 -
noexcept函数内禁止throw表达式,连throw 42;都不合法
模板函数的 noexcept 声明要小心推导失效
函数模板的 noexcept 不能靠“看代码”猜,必须显式写出条件表达式,否则默认按非 noexcept 处理。比如 template<typename t> void swap(T& a, T& b) noexcept</typename> 实际等价于 noexcept(false),除非你写成 noexcept(noexcept(a.swap(b))) 这类嵌套形式。
标准库里的 std::swap 就是这么做的:它用 noexcept 检查内部调用是否无异常,再决定自身是否 noexcept。
- 模板中避免裸写
noexcept,一定要配合noexcept(...)表达式 - 用
std::is_nothrow_swappable_v<t></t>等类型特征辅助判断,比手写条件更可靠 - 注意 ADL 查找可能影响
noexcept表达式的求值结果,不同命名空间下同名函数行为可能不同
operator new 不抛,也没保证 std::string 的移动构造一定 noexcept(直到 C++17 才对基础字符串类型加了约束)。所以每次加 noexcept,本质是在做一次轻量级契约审查。


















