几个 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
- 添加了
noexcept
代替之
先看看之前的 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()
结束程序;如果你觉得这不好,你可能会想要去替换掉这个全局的处理函数,你会发现你可以:
- 和默认行为一样,调用
std::terminate()
自尽。 - 抛出异常,但是:
- 这个异常存在于
throw(...)
列表中,那么正常抛出。 - 这个异常不存在于列表中,但是
std::bad_exception
在,那么抛std::bad_exception
。 - 这个异常不存在于列表中,而且
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
:
- 需要更换错误报告手段(例如:实现 C 接口、进入了
noexcept
函数、多线程、系统回调……) - 允许这种异常的发生(例如:如果
GetEntryDataFromZip()
会在 zip 文件数据损坏时抛出异常,那么DisplayImageFileInZip()
就不应该捕获;而CopyAllEntriesToAnotherZip()
则可以选择捕获这个异常,生成一个空的 zip 文件。) - 有替代方案
捕获基类构造产生的异常真是别扭啊, 学习了...
不过话说回来, 构造函数的异常就算被 catch, 对象状态不是会有问题么, 这种情况下还是倾向于速死比较好吧?
确实...所以在构造函数里 catch 了以后,即使你不继续 throw 出来,系统也会强制帮你 throw 出来的 = = 貌似很意义不明的样子……