C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。 在实践中:

  • 一个操作或者函数可能会抛出一个异常;
  • 一个操作或者函数不可能抛出任何异常。

使用noexcept指定函数或操作不会抛出异常,会给编译器更大的优化空间,同时减少运行时开销。

当函数被标记为 noexcept 时,编译器可以进行以下优化:

  • 避免生成额外的异常处理代码,从而减少代码大小和提高执行效率。
  • 优化函数调用栈的管理,因为不需要为可能的异常保留额外的空间。

    noexcept说明符

    noexcept说明符指定函数是否会抛出异常。

    noexcept用法

void func() noexcept { }              // 1

void func() noexcept(expression) { }  // 2

第2种写法将根据表达式的真假来判断函数是否抛出异常。 第1种写法等价于noexcept(true)

noexcept并不能保证函数不会抛出异常。如果函数在运行时仍然抛出了异常,那么程序会调用std::terminate()函数来终止程序的执行。

默认noexcept的函数

  • 默认构造函数
  • 默认拷贝构造函数
  • 默认拷贝赋值函数
  • 默认移动构造函数
  • 默认移动赋值函数
  • 析构函数
  • delete运算符

注意:自定义析构函数带有noexcept,但自定义构造函数不带有noexcept。

什么时候应该使用noexcept

当为一个类设计移动系列(移动构造、移动复制)函数时,如果可以,最应该为其加上noexcept,以便此类在使用标准库容器时可以用移动操作来代替拷贝。

以vector为例:很多标准库容器需要特定时机来执行扩容操作(把元素从旧内存拷贝到新开辟的内存,再析构旧内存中的元素),针对这种情况明显可以用移动操作来优化。但标准库的做法是:如果容器中类的移动操作函数带有noexcept声明,则使用移动操作来代替拷贝;如果没有noexcept声明,则用拷贝来完成扩容

noexcept运算符

noexcept运算符进行编译时检查,在表达式声明不会抛出任何异常的情况下返回true。

noexcept运算符是一个一元运算符,它的返回值是一个bool类型的右值常量表达式,用于表示给定的表达式是否会抛出异常。

#include <iostream>
#include <utility>
#include <vector>

void may_throw();
void no_throw() noexcept;
auto lmay_throw = []{};
auto lno_throw = []() noexcept {};

int main()
{
    std::cout << std::boolalpha
        << "may_throw() 可能会抛出异常吗?" << !noexcept(may_throw()) << '\n'
        << "no_throw() 可能会抛出异常吗?" << !noexcept(no_throw()) << '\n'
        << "lmay_throw() 可能会抛出异常吗?" << !noexcept(lmay_throw()) << '\n'
        << "lno_throw() 可能会抛出异常吗?" << !noexcept(lno_throw()) << '\n'
}

输出:

may_throw() 可能会抛出异常吗?true
no_throw() 可能会抛出异常吗?false
lmay_throw() 可能会抛出异常吗?true
lno_throw() 可能会抛出异常吗?false
#include <iostream>
#include <utility>
#include <vector>
class T {
public:
    ~T(){} // 析构函数妨碍了移动构造函数
           // 复制构造函数不会抛出异常
};

class U {
public:
    ~U(){} // 析构函数妨碍了移动构造函数
           // 复制构造函数可能会抛出异常
    std::vector<int> v;
};

class V {
public:
    std::vector<int> v;
};

int main()
{
    T t;
    U u;
    V v;

    std::cout << std::boolalpha
        << "T(T 右值) 可能会抛出异常吗?" << !noexcept(T(std::declval<T>())) << '\n'
        << "T(T 左值) 可能会抛出异常吗?" << !noexcept(T(t)) << '\n'
        << "U(U 右值) 可能会抛出异常吗?" << !noexcept(U(std::declval<U>())) << '\n'
        << "U(U 左值) 可能会抛出异常吗?" << !noexcept(U(u)) << '\n'
        << "V(V 右值) 可能会抛出异常吗?" << !noexcept(V(std::declval<V>())) << '\n'
        << "V(V 左值) 可能会抛出异常吗?" << !noexcept(V(v)) << '\n';
}

输出:

~T() 可能会抛出异常吗?false
T(T 右值) 可能会抛出异常吗?false
T(T 左值) 可能会抛出异常吗?false
U(U 右值) 可能会抛出异常吗?true
U(U 左值) 可能会抛出异常吗?true
V(V 右值) 可能会抛出异常吗?false
V(V 左值) 可能会抛出异常吗?true