noexcept到底是个啥?
写C++的时候,你可能见过函数后面跟着个noexcept,像是这样:
void cleanup() noexcept;
void risky_operation() noexcept(false);这玩意儿不是装饰,它是C++11引入的一个关键字,用来告诉编译器:这个函数会不会抛异常。别小看这一点信息,它能影响性能、优化,甚至决定程序能不能正确运行。
基本语法:什么时候用noexcept
最简单的形式就是在函数声明或定义末尾加上noexcept,表示这个函数不会抛出任何异常:
void fast_swap(int& a, int& b) noexcept {
int temp = a;
a = b;
b = temp;
}如果加上noexcept(false),那就明确说:我可能会抛异常。这种写法比较少见,多数时候是用来模板里做条件判断。
带条件的noexcept怎么玩
有时候你不确定一个函数会不会抛异常,比如它内部调用了别的函数。这时候可以用noexcept加一个布尔表达式:
template <typename T>
void swap_wrapper(T& a, T& b) noexcept(noexcept(a.swap(b))) {
a.swap(b);
}这里的双重noexcept看着绕,其实不难:外层是关键字,内层是操作符,用来判断a.swap(b)会不会抛异常。整个表达式的意思是:如果a.swap(b)是noexcept的,那swap_wrapper也是noexcept。
为什么要在意这个?性能和移动语义
来看看vector扩容的场景。当你往vector里塞对象时,如果空间不够,就得搬数据。这时候如果类型支持移动构造,而且移动是noexcept的,STL就会优先用移动;否则就乖乖拷贝。
比如你写了个类:
class MyData {
public:
MyData(MyData&& other) noexcept {
// 移动资源,不抛异常
}
};加上noexcept后,vector在扩容时就能大胆地移动对象,避免昂贵的拷贝操作。要是没加,哪怕你的移动函数从不抛异常,编译器也不敢用,因为它不知道。
不小心违反noexcept会怎样?
如果你声明了一个函数为noexcept,结果里面却抛了异常,程序会直接调用std::terminate(),也就是直接终止,连抢救的机会都没有。
void quiet_function() noexcept {
throw std::runtime_error("出事了"); // 危险!程序会崩溃
}所以,noexcept不是随便加的。除非你100%确定函数不会抛异常,包括它调用的所有函数。
实际项目中的使用建议
在工具类、资源管理类中,尽量给移动构造、移动赋值加上noexcept。标准库组件(比如智能指针、容器)都依赖这个来做优化。
另外,像析构函数,默认就是noexcept的。手动加不加都行,但千万别让它抛异常,否则后果很严重。
接口设计时也可以考虑用noexcept传递意图。比如一个清理函数,明确标为noexcept,别人看了就知道调用它不用担心异常。