伤情最是晚凉天,憔悴厮人不堪怜。邀酒催肠三杯醉,寻香惊梦五更寒。钗头凤斜卿有泪,荼蘼花了我无缘。小楼寂寞新雨月,也难如钩也难圆。
1. 能不能抛出?
答案是:能抛出,但是只能抛出一点点。理论上来说,我们在语法层面并不禁止在构造函数和析构函数中抛出异常。但是种种迹象表明(网络上的各种资料):不要在析构函数中抛出异常。
2. 构造函数中的异常
构造函数中抛出异常具有一定的合理性。比如说我们想要在构造函数中打开一个文件什么的,如果这个文件指针是空的,那我当然是想抛出一个异常的。但是这其中有很多东西需要学习、有很多细节要注意。
首先是我们需要警惕内存泄漏。在构造函数中抛出异常,相当于构造失败了,生命周期没有开始。程序会自动地让这个类的对象的成员对象都析构。但是这个过程中,对象的成员指针并不会被析构掉。所以如果我们有这样的代码:
class A{struct B{int a;B() { cout << "ctor" << endl; }~B() { cout << "dtor" << endl; }};B b;int *a;A(){a = new int;B b;throw 1;}};// output: ctor \n dtor
上面的 B 对象成功被析构了,但是`int *`就会泄露掉了。所以如果一定要在构造函数里抛出异常,那么务必处理好指针的问题防止内存泄漏。一种做法是:把各种指针包装在一个 struct 里面,这样以一个辅助的 struct 对象来防止泄露。<br />最后,如果一个对象在构造的时候抛出了异常,那么它的析构函数是不会被调用的,因为它的生命周期没有开始。
3. 不要在析构中抛出异常
各种书籍和教程都告诉我们,不要在析构中抛出异常。总结了一下,有这样几个原因:
- 析构并不是显式调用的,这会让一个异常可能在不知不觉中被抛出了,而没有人能够去接它。
- 析构抛出异常也可能会让后续的释放资源不能正常进行,造成内存泄露(不过这不是主要原因,因为这是可以通过重构代码以及上文所述的加一个辅助的结构体解决的)
- 如果我们的代码本身就在某个地方抛出了异常,C++的机制会调用各自的析构函数来把当前作用域的临时变量给析构了,这个时候如果你的析构也抛出异常,此时两个异常同时存在会让程序直接 terminate,这是C++的特性。这个原因应该是最关键的原因。
实际上,如果你在析构函数中抛出了异常,那么编译器会给你这样的提示:
test.cpp:11:15: warning: throw will always call terminate() [-Wterminate]throw 1;^test.cpp:11:15: note: in C++11 destructors default to noexcept
也就是说析构函数默认是 noexcept 的,所以一旦你在析构中抛出了异常,那么程序会自动中止。
