C++11-noexcept

1.noexcept

  在C++11中,noexcept关键字有两种主要用法:作为异常说明符(Exception Specifier)和作为运算符(Operator)。下面我们来研究这两种用法。

2.noexcept作为异常说明符

在异常处理的代码中,我们可能会看到如下的异常声明表达式:
void excpt_func() throw(int, double) { ... }
  在excpt_func函数声明之后,我们定义了一个动态异常生命throw(int, double),该声明指出了excpt_func可能抛出的异常类型。实际上,该特性现在很少被使用,在C++中被弃用了,而表示函数不会抛出异常的动态异常生命throw()也被新的noexcept所替代。
  noexcept表示其声明的函数不会抛出异常。在C++11中如果noexcept声明的函数抛出了异常,编译器可以选择直接调用std::terminate()函数来终止程序的运行。
  noexcept作为异常说明符有两种形式,一种就是简单地在函数生命后加上noexcept关键字。如:
void except_func() noexcept;
  另一种则是接受一个常量表达式作为参数,如:
void except_func() noexcept(常量表达式);
  常量表达式的结果会被转换成一个bool类型的值。该值为true,表示函数不会抛出异常,反之,则有可能抛出异常。这里,不带常量表达式的noexcept相当于声明了noexcept(true),即不会抛出异常。
  在通常情况下,C++11中使用noexcept可以有效阻止异常的传播与扩散。如以下例程:

#include <iostream>
using namespace std;

void Throw() { throw 1; }
void NoBlockThrow() { Throw(); }
void BlockThrow() noexcept { Throw(); }

int main() {
    try {
        Throw();
    }
    catch(...) {
        cout << "Found throw." << endl;
    }

    try {
        NoBlockThrow();
    }
    catch(...) {
        cout << "Throw is not blocked. " << endl;
    }

    try {
        BlockThrow();
    }
    catch (...) {
        cout << "Found throw 1." << endl; //terminate called after throwing an instance of 'int'
    }
}
//编译选项:g++ -std=c++11 xxx.cpp

编译并运行:

ydqun@ydqhost chapter02 % g++ -std=c++11 12.cpp                                                                                                                                               [0]
ydqun@ydqhost chapter02 % ./a.out                                                                                                                                                             [0]
Found throw.
Throw is not blocked.
terminate called after throwing an instance of 'int'
[1]    963448 IOT instruction  ./a.out

在上述代码中,我们可以看到出了BlockThrow()调用了std::terminate中断程序的执行,阻止了异常的继续传播外,ThrowNoBlockThrow()都抛出了异常,并且被捕获到。

3.noexcept作为运算符

  而noexcept作为一个操作符时,通常可以用于模板。比如:

template <class T>
  void func() noexcept(noexcept(T())) {}

1.外层noexcept:上文所介绍的noexcept作为异常说明符的用法

外层的noexcept(...)是函数的 异常说明符,用于声明func()是否可能抛出异常:

  • 语法:noexcept后跟一个布尔常量表达式,决定函数是否标记为noexcept
    • 若表达式为 true,则 func() 是 noexcept 的(不抛出异常)。
    • 若为 false,则允许 func() 抛出异常。

2.内层noexcept:运算符

内层的noexcept(T())noexcept运算符,用于在编译期检查表达式T()是否会抛出异常。

  • 语法:noexcept(expression)
    • expression的求值 不会抛出异常,则返回true
    • 若可能抛出异常,则返回false

在此例中,noexcept(T()) 检查类型T的默认构造函数(T())是否可能抛出异常。

3.组合逻辑

noexcept(noexcept(T()))
//     ↑          ↑
// 说明符       运算符
  • 整体含义:
    func()noexcept属性取决于T()的异常行为。
    • T()noexcept的,则noexcept(T())true, 所以func也被标记为noexcept(true)
    • T()可能抛出异常,则noexcept(T())false,所以func()的异常说明符为noexcept(false)(即允许抛出异常)。

4.验证与调试

我们可以通过noexcept运算符和static_assert验证条件:

#include <iostream>

class A{
public:
    A() noexcept {} //默认构造函数不抛出异常
};

class B  {
public:
    B() {} //默认构造函数可能跑出异常
};

template <class T>
void func() noexcept(noexcept(T())) {}


int main() {

    static_assert(noexcept(A()), "A() should be noexcept.");//通过
    static_assert(!noexcept(B()), "B() should not be noexcept.");//通过

    static_assert(noexcept(func<A>()), "func<A> should be noexcept");//通过
    static_assert(!noexcept(func<B>()), "func<B> should not be noexcept");//通过

    return 0;
}

4.类析构函数默认是noexcept(true)

在C++11中,类的析构函数默认是隐式标记为noexcept(true)的,但需要满足一下条件:

  • 1.隐式生成的析构函数
    • 如果用户未显示声明析构函数,且所有基类和非静态成员的析构函数均为noexcept(true),则编译器生成的默认析构函数为noexcept(true)。若任一基类或成员的析构函数为noexcept(false),则隐式生成的析构函数为noexcept(false)
  • 2.用户显示声明的析构函数
    • 若用户声明函数但未指定noexcept,则默认标记为noexcept(true)。例如:
~MyClass() {} // 默认为 noexcept(true)

此时,若析构函数抛出异常且为捕获,程序将调用std::terminate()

  • 3.显示允许异常:
    若通过noexcept(false)显式声明析构函数,则允许析构函数抛出异常;否则,则析构函数默认是noexcept(true)
~MyClass() noexcept(false) { /* 可能抛出 */ }
  • 我们可以看一下以下例子:
#include <iostream>
using namespace std;

struct A {
    ~A() { throw 1; }
};

struct B {
    ~B() noexcept(false) { throw 2; }
};

struct C {
    B b;
};

void funA() { A a; }
void funB() { B b; }
void funC() { C c; }

int main() {
    try {
        funB();
    }
    catch(...) {
        cout << "caught funB." << endl;
    }

    try {
        funC();
    }
    catch(...) {
        cout << "caught funC." << endl;
    }

    try {
        funA(); //terminate called after throwing an istance of 'int'
    }
    catch (...) {
        cout << "caught funA." << endl;
    }
}

  上述例子中,类A没有显示声明noexcept,所以类A其细狗函数为默认noexcept(true),从而阻止了异常的扩散。而类B由于显式声明析构函数为noexcept(false),所以无论是类B还是包含了B类成员变量的类C,其析构函数都是可以抛出异常的。
总结:
默认情况下(未显式声明析构函数且基类/成员均无潜在抛出的析构函数),C++11中类的析构函数是noexcept(true)的。这一设计旨在避免栈展开时因析构函数抛异常导致程序终止,符合资源释放的安全实践。

posted @ 2025-03-05 11:18  ydqun  阅读(91)  评论(0)    收藏  举报