TimothyQiu's Blog

keep it simple stupid

几个 C++ 异常的零碎知识

分类:技术

在派生类构造函数中捕获基类构造函数异常

class Base {
public:
    Base()
    {
        throw std::runtime_error("Error from Base");
    }
};

class Derived : public Base {
public:
    Derived()
    {
        try {
        } catch(std::exception const& e) {
        }
    }
};

类似于这样的代码,是捕获不到异常的,因为执行到构造函数的函数体中时,基类已经构造完毕了。

索性 C艹 有一个神奇的写法:

Derived::Derived()
try : Base() {
} catch(...) {
}

没错,我没有漏写大括号……你完全可以直接把一个 try {} catch {} 当作函数体……同理,对于一般函数这么写也行:

void foo()
try {
} catch(...) {
}

当然,构造函数的 catch 里是无论如何都会帮你确保有一个异常被抛出去的。(你不显式抛,它就帮你把被捕获的异常抛出去。)

后来发现这个东西学名叫做:Function-try block。

C++11 的异常处理

查了一番,似乎大的改变在于:

先看看之前的 Dynamic Exception Specification 是什么:

void foo();              // (1) 允许抛出异常
void foo() throw(X, Y);  // (2) 只允许抛出 X 和 Y 异常
void foo() throw();      // (3) 不允许抛出异常

坑爹之处在于第 2 点:Dynamic Exception Specification 是在运行时进行的,于是,为了确保只能抛出指定类型的异常,就会生成额外代码降低执行效率。

最扭曲的是可自定义的 unexcepted 处理函数。所谓 unexcepted 处理函数,就是一旦你抛出了 throw(...) 中没有出现的类型,系统会去调用的那个函数。默认的 unexcepted 函数直接调用 std::terminate() 结束程序;如果你觉得这不好,你可能会想要去替换掉这个全局的处理函数,你会发现你可以:

  1. 和默认行为一样,调用 std::terminate() 自尽。
  2. 抛出异常,但是:
    1. 这个异常存在于 throw(...) 列表中,那么正常抛出。
    2. 这个异常不存在于列表中,但是 std::bad_exception 在,那么抛 std::bad_exception
    3. 这个异常不存在于列表中,而且 std::bad_exception 也不在,那么系统 std::terminate() 之。

重点在于,这是一个「全局」的处理函数。如果有人自定义这个,相当于如果要写 throw 列表,就要把 std::bad_exception 加上。

于是 C++11 因为这个在实践中基本没人用的东西而废弃了这个名字很长的功能,用 noexcept 取而代之:

void foo();                                 // (1) 允许抛出异常,相当于 noexcept(false)
void foo() noexcept(constant-expression);   // (2) expression 成立时不允许抛出异常
void foo() noexcept;                        // (3) 不允许抛出异常,相当于 noexcept(true)

也就是将原先的 (2) 去掉,然后扩展一下原先的 (3)。一旦不允许抛异常的函数抛出了异常,那么直接 std::terminate() 结束运行。

对应还有一个同名的操作符 noexcept,用来在编译时判断表达式是否允许抛出异常(遇到函数时只通过其声明时的 noexcept 与否判断)。

noexcept(1 + 2)     // false
noexcept(throw 1)   // true
noexcept(foo())     // 返回 foo() 声明时的 noexcept 与否

最好在哪里捕获异常

处处都 catch 的话,就反而比 if 麻烦了。据推荐,最好在这些地方 catch


参考内容:Jon Kalb: Exception-Safe Coding in C++

C++C++11

已有 2 条评论 »

  1. 捕获基类构造产生的异常真是别扭啊, 学习了...

    不过话说回来, 构造函数的异常就算被 catch, 对象状态不是会有问题么, 这种情况下还是倾向于速死比较好吧?

    1. 确实...所以在构造函数里 catch 了以后,即使你不继续 throw 出来,系统也会强制帮你 throw 出来的 = = 貌似很意义不明的样子……

添加新评论 »