移动构造函数和移动赋值运算符必须加 noexcept,否则 std::vector 等容器扩容时会退化为拷贝;析构函数应显式声明 noexcept 以保障栈展开安全;noexcept 是函数类型的一部分,声明与定义必须一致。

移动构造函数和移动赋值运算符必须加 noexcept,否则标准容器(如 std::vector)在扩容时会退回到拷贝,性能可能暴跌数倍。
哪些函数不加 noexcept 会导致容器行为降级
标准库容器在内部重分配内存时,会检查元素类型的移动操作是否标记为 noexcept。若未标记,视为“不安全移动”,直接放弃移动、改用拷贝构造——哪怕你的移动实现本身完全不抛异常。
-
std::vector::resize()、push_back()、reserve()都依赖此判断 -
std::deque和std::basic_string同样适用 -
std::optional、std::variant的赋值/构造也会据此选择路径
示例:一个没加 noexcept 的移动构造函数
struct Widget {
Widget(Widget&& other) { /* 资源转移逻辑 */ } // ❌ 没写 noexcept
};
此时 std::vector<widget> v; v.reserve(1000);</widget> 会触发 1000 次拷贝,而非移动。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
析构函数默认是 noexcept,但显式写出更安全
编译器给用户定义的析构函数默认加上 noexcept(true),但若你调用了可能抛异常的代码(比如某成员的 close() 方法没标 noexcept),就会隐式变成 noexcept(false),进而破坏栈展开安全性。
- 显式写成
~Widget() noexcept可强制约束,也避免因成员变更导致意外降级 - 若真需要在析构中处理异常(极少见),必须写
noexcept(false)并自行捕获,否则第一次异常未处理就调用std::terminate() - 第三方库类型若析构未标
noexcept,它所在的类也很难安全标noexcept
模板函数中用 noexcept(...) 表达式做条件声明
纯 noexcept 是布尔常量,而 noexcept(expr) 是编译期运算符,返回 true 或 false。它不是运行时检查,只看 expr 中所有函数的声明是否带 noexcept。
- 常见模式:
template<typename t> void swap(T& a, T& b) noexcept(std::is_nothrow_swappable_v<t>);</t></typename> - 错误写法:
noexcept(f(x))其中f声明为noexcept,但x是运行时变量——不影响结果,因为只查声明 - 滥用风险:嵌套太深(如
noexcept(noexcept(g()) && noexcept(h())))可读性差,且无实际收益;多数场景直接用类型特质更清晰
加了 noexcept 却抛异常的后果不是报错,而是静默崩溃
编译器不会帮你验证承诺是否成立。一旦违反,程序立即调用 std::terminate(),不展开栈、不执行 catch、不释放局部对象——调试时表现为无提示的进程退出,容易误判为段错误或死锁。
- 典型踩坑点:
new分配失败抛std::bad_alloc;std::vector::at()越界抛std::out_of_range;调用了未标noexcept的第三方函数 -
noexcept函数里允许try/catch,只要最终不向外传播异常即可 - 真正难的是“敢不敢保证”:你要对整条调用链(包括所有
operator[]、new、成员函数、模板实参类型的操作)负全责
最常被忽略的一点:noexcept 是函数类型的一部分。声明和定义必须完全一致,否则链接失败或编译报错——这不是警告,是硬性约束。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










