noexcept是函数异常安全契约,承诺绝不抛异常,违者调用std::terminate;它是函数类型一部分,影响移动语义与容器性能,需谨慎标注。

noexcept用在函数声明后面表示不抛异常
加noexcept不是为了“告诉编译器别抛异常”,而是向编译器和调用者承诺:这个函数**绝不会抛出任何异常**。一旦违反(比如函数体内调了可能抛异常的代码又没捕获),程序直接调用std::terminate()——不是抛错、不是返回错误码,是立刻终止。
常见误用是给一个调了std::vector::push_back()的函数加noexcept,但push_back可能因内存不足抛std::bad_alloc,这时加了就等于埋雷。
- 只对确定不抛异常的函数加,比如空函数、纯算术运算、
delete裸指针、std::move后的资源转移 - 移动构造函数/移动赋值运算符若想被标准容器(如
std::vector)优先选用,强烈建议加noexcept;否则容器扩容时可能退化为拷贝而非移动 -
noexcept是函数类型的一部分,void f() noexcept和void f()是两个不同类型,不能互相赋值给函数指针
noexcept(expr)是条件式noexcept,编译期求值
noexcept后面跟括号,里面是个常量表达式,编译器会在编译时判断这个表达式是否可能抛异常。典型用法是转发移动操作:
template<typename t>
T&& move(T& t) noexcept(noexcept(t = static_cast<t>(t))) {
return static_cast<t>(t);
}</t></t></typename>
这里外层noexcept(...)的括号里又套了一个noexcept(...):内层检查t = ...是否可能抛异常,结果作为外层的条件。这种写法让std::move的异常规范自动适配被移动类型的赋值运算符。
- 表达式里不能有运行时变量,只能是类型操作、
decltype、sizeof、调用已知noexcept的函数等 - 如果表达式本身语法错误(比如调了不存在的函数),整个
noexcept(expr)视为false,即函数不noexcept - 用
noexcept(expr)比硬写noexcept(true)或noexcept(false)更安全,尤其在模板中
noexcept对移动语义和标准容器性能影响很大
当std::vector扩容需要重新分配内存并移动元素时,它会先检查元素类型的移动构造函数是否noexcept。如果是,就放心移动;否则,为保证强异常安全,改用拷贝构造——哪怕移动本身更快,也宁可慢一点。
- 自定义类的移动构造函数若涉及
new/malloc、文件IO、网络调用等,基本不能标noexcept - 若移动只交换指针或整数(如
std::unique_ptr内部),可以且应该加noexcept - 用
static_assert验证:例如static_assert(noexcept(MyType(std::declval<mytype>())));</mytype>能提前暴露移动构造未正确标注的问题
析构函数默认noexcept,但显式写出来更清晰
C++11起,用户声明的析构函数默认带noexcept(即使没写),而基类或成员析构若抛异常,会导致std::terminate。所以除非你明确知道所有成员和基类析构都安全,否则别在析构里做可能抛异常的事。
- 如果真要写可能抛异常的清理逻辑(极少见),得显式写成
~MyClass() noexcept(false),但这样该类就不能作为std::vector等容器的元素 - 多数情况保持默认即可,但显式加上
noexcept(如~MyClass() noexcept)能让意图更明确,也避免未来误加抛异常代码而不自知 - 注意:继承体系中,派生类析构函数的
noexcept规格不能比基类更宽松;若基类析构是noexcept,派生类也必须是
最容易被忽略的是:noexcept不是优化开关,也不是异常拦截器;它是契约,一旦签了就得守。调试时如果程序突然静默退出,检查noexcept函数里有没有偷偷调了可能抛异常的第三方库函数,比查内存越界还容易漏掉。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











