C++11 的noexcept是替代 C++98 废弃异常说明的核心特性,作为函数接口的关键组成部分,仅当确定函数永远不会抛出异常时声明;其核心价值是让编译器生成更高效的目标代码,同时为调用者 / STL 提供明确的异常安全承诺,且不可为了加noexcept扭曲函数实现,大多数函数因 “异常中立” 无需声明。
noexcept的核心价值
性能层面:编译器最大化优化代码
- 逻辑:
noexcept承诺 “函数异常传播时可直接终止程序”,编译器无需保证调用栈完整展开、局部对象按构造反序析构,因此可剔除冗余代码,生成更精简高效的机器码;
- 对比差异:
noexcept函数的优化度远高于 C++98 的throw()或无异常声明的函数(后两者需预留栈展开的冗余代码);
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void moveWidget(Widget&& w) noexcept { }
void moveWidget(Widget&& w) throw() { }
void moveWidget(Widget&& w) { }
|
语义层面:作为接口承诺,支撑 STL 核心逻辑
- 逻辑:
noexcept是函数接口的关键属性(与const同等重要),调用者 / STL 会依赖该承诺判断是否安全使用高性能逻辑;
- 支撑场景:
- 移动操作(构造 / 赋值):STL 容器仅当移动操作
noexcept时,才会用 “移动” 替代 “拷贝”(避免移动抛异常破坏异常安全);
1 2 3 4 5 6 7 8 9 10 11
| class Widget { public: Widget(Widget&& other) noexcept { } };
std::vector<Widget> vec; Widget w; vec.push_back(std::move(w));
|
swap函数:高层次结构(数组、std::pair)的swap是否noexcept依赖底层元素,加noexcept可让整个层级受益;
1 2 3 4 5 6 7 8 9 10 11
| class Widget { public: void swap(Widget& other) noexcept { } };
Widget arr1[5], arr2[5]; std::swap(arr1, arr2);
|
- 析构 / 内存释放函数:C++11 默认隐式
noexcept,无需显式声明,保证内存管理和对象销毁安全;
1 2 3 4 5 6 7 8 9 10 11
| class Widget { public: ~Widget() { } };
void operator delete(void* ptr) { }
|
noexcept的适用 / 不适用场景
必加noexcept的场景
- 移动构造 / 移动赋值函数(STL 性能优化的核心);
swap函数(STL 算法核心,层级依赖其noexcept属性);
- 析构函数、
operator delete/delete[](默认隐式noexcept,无需显式声明);
- 无前置条件的宽泛契约函数,且确定永不抛异常;
1 2 3 4
| int calculateSum(int a, int b) noexcept { return a + b; }
|
绝对不加noexcept的场景
- 异常中立函数:自身不抛异常,但内部调用可能抛(加了会导致异常传播时程序直接终止);
1 2 3 4 5 6
| void processData(const std::string& data) { char* buf = new char[data.size()]; }
|
- 严格契约函数:有前置条件,检查条件时无法抛异常调试;
1 2 3 4 5 6 7
| void processShortString(const std::string& s) { if (s.length() > 32) { throw std::invalid_argument("string too long"); } }
|
- 为加
noexcept扭曲实现(如捕获所有异常转状态码,增加代码复杂度);
1 2 3 4 5 6 7 8 9 10
| int riskyFunc() noexcept { try { return callUnsafeFunc(); } catch (...) { return -1; } }
|
- 无法保证长期永不抛异常的函数(
noexcept是接口承诺,修改会影响所有调用者);
注意点
- 编译器不校验
noexcept与函数实现的一致性:noexcept函数可调用无noexcept但实际不抛的函数(如 C 库、旧 C++98 函数),编译器不报错;
1 2 3 4
| void processString(const char* str) noexcept { std::strlen(str); }
|
- 正确性优先于优化:永远不要为了加
noexcept牺牲函数逻辑正确和接口稳定;
- 大多数函数是异常中立的,无需加
noexcept;
总结
- 仅当确定函数永不抛异常时,才声明
noexcept,其是函数接口的重要组成部分。
noexcept函数可被编译器极致优化,且是 STL 移动语义、swap函数高效安全使用的核心支撑。
- 移动操作、
swap函数是noexcept的核心适用场景,异常中立函数、严格契约函数等绝对不加noexcept。