noexcept是函数异常安全契约而非开关,声明后编译器优化栈展开逻辑,违反则直接调用std::terminate;必须确保整个调用链无异常可能,移动操作未标记noexcept会导致容器降级为拷贝。

noexcept 用在函数声明里,不是开关,是契约
它告诉编译器“这个函数绝不会抛出异常”,编译器信了,就可能做优化(比如省掉栈展开逻辑),但你写错了,程序直接调用 std::terminate——不报错、不提示、不回溯,直接杀进程。
常见错误现象:noexcept 标在调用了可能抛异常的函数上,比如没加 noexcept 的自定义构造函数、std::vector::push_back、或带 throw 的旧式异常说明(throw())混用;运行时崩溃但堆栈停在 std::terminate,根本看不到源头。
- 只对明确可控的函数加
noexcept:空构造/析构、swap、纯算术运算、delete操作等 - 成员函数加
noexcept前,检查所有调用链:包括基类函数、成员对象的函数、operator new/delete 是否也标记了 -
noexcept是函数类型的一部分,void f() noexcept和void f()是两个不同类型,不能用于虚函数重写时随意切换
noexcept 运算符:运行时判断是否真能不抛,但别滥用
noexcept 不只是关键字,还是个一元运算符,比如 noexcept(func()) 返回 bool 编译时常量,常用于模板 SFINAE 或 constexpr if 分支。
使用场景有限:多见于标准库实现(如 std::move_if_noexcept)或泛型容器的移动策略选择;普通业务代码极少需要手动判断。
立即学习“C++免费学习笔记(深入)”;
- 它只看函数声明里的
noexcept说明,不实际执行函数,也不分析函数体——哪怕函数体里写了throw,只要声明是noexcept,noexcept(func())就返回true - 不要用它替代 try/catch:它不能捕获异常,也不能改变行为,只是编译期“查户口”
- 和
constexpr混用要小心:若表达式含非字面类型或运行时值,noexcept(...)可能无法在常量表达式中求值
移动操作加 noexcept 是性能关键,不加可能降级为拷贝
标准容器(如 std::vector、std::deque)在扩容、重哈希或算法(如 std::sort)中做元素搬移时,会优先选移动而非拷贝——但前提是移动构造/赋值被标记为 noexcept。否则,容器为保异常安全,退回到更慢的拷贝路径。
典型表现:明明写了移动函数,std::vector::resize 却触发大量拷贝构造,CPU 火焰图里 copy 占比异常高。
- 移动构造函数和移动赋值运算符默认不
noexcept,必须显式加上:T(T&&) noexcept - 如果移动函数内部调用了可能抛异常的成员(比如某个成员的移动构造没加
noexcept),整个函数就不能加——得先修复依赖链 - 用
static_assert(noexcept(T(std::declval<t>())), "")</t>在编译期验证,比运行时才发现靠谱得多
noexcept(true) 和 noexcept(false) 是冗余写法,少用
noexcept 等价于 noexcept(true),noexcept(false) 等价于不写;C++17 起还支持 noexcept(expr) 形式,但日常几乎不需要。
容易踩的坑:有人把 noexcept(true) 当成“加强版”,其实语义完全一样;而 noexcept(false) 显式声明“可能抛异常”,但标准库从不依赖这个信息,编译器也不据此生成额外逻辑——纯属占字符。
- 写
noexcept就够了,别画蛇添足加括号和布尔字面量 -
noexcept(expr)只在极少数元编程场景有用,比如根据模板参数条件决定是否noexcept,但绝大多数项目用不到 - 注意 C++20 引入的
[[no_unique_address]]等属性和noexcept无关,别混淆
真正难的不是语法,是厘清整个调用链里每个环节是否真的能承诺不抛——一个第三方库的 std::string 构造函数没标 noexcept,就可能让你的移动函数不敢标,连锁反应。这种依赖关系看不见摸不着,只能靠静态断言一层层卡死。









