写代码就像做饭,火候掌握不好,饭就容易糊。在C++里,异常处理就是那把火,用得好程序稳如老狗,用不好动不动就崩给你看。很多人一遇到异常就try-catch堆满屏幕,结果性能拖垮,还难维护。其实有个小技巧能帮你省下不少调试时间——那就是noexcept。
noexcept不是禁用异常,而是说“我不会抛”
很多人一听noexcept以为是关掉异常功能,其实它更像是一个承诺:这个函数不会抛出异常。比如你写了个交换两个数的函数,逻辑简单,根本不会出错,那就干脆标上noexcept,告诉编译器和同事:“放心调,不会炸。”
void swap(int& a, int& b) noexcept {
int temp = a;
a = b;
b = temp;
}
这么一写,编译器就知道可以大胆优化,甚至在某些容器操作里自动启用更高效的移动语义。要是你不标,编译器就得留后路,性能自然打折扣。
标错了会怎样?程序直接退出
noexcept可不是随便标的。一旦你声明了noexcept,结果函数里真抛了异常,程序不会进catch,而是直接调用std::terminate(),整个进程就没了。这就像跟朋友说“我绝对不迟到”,结果迟到了,信誉就崩了。
void risky_function() noexcept {
throw std::runtime_error("出事了"); // 程序直接终止
}
所以标noexcept前得想清楚,是不是真的稳。数学计算、指针交换、基本类型赋值这类操作通常没问题,但凡涉及内存分配、文件读写、网络请求的,就得掂量掂量。
条件式noexcept让灵活性翻倍
有时候你只想在特定条件下才承诺不抛异常。比如模板函数,类型T的移动构造是否异常安全,取决于T自己。这时候可以用noexcept加个条件:
template<typename T>
void wrapper_move(T& a, T& b) noexcept(noexcept(std::move(a))) {
a = std::move(b);
}
这里的外层noexcept后面跟了个表达式,意思是“如果std::move(a)不会抛,那我也不抛”。这种嵌套写法看着绕,实则是高手常用的保命写法,既保证性能又不失安全。
标准库里的noexcept随处可见
打开STL源码,你会发现vector的移动构造函数、swap、智能指针的reset这些高频操作基本都标了noexcept。为什么?因为它们设计时就考虑了异常安全,而且被调用成千上万次,一点点性能提升都值得争取。
你自己写的工具函数如果也走这条路,久而久之项目整体稳定性会上一个台阶。别人调你的接口时也能放心做优化,相当于间接帮团队省了排查崩溃的时间。