没有学不会的C++(异常处理和析构函数)
C++ 对待异常处理有两个规则
- 如果在
try...catch
中有异常抛出,则在catch
执行前,会先将try
语句块对应的栈清空 - C++ 不允许在同一个
try...catch
中处理1个以上的异常,如果发生此种情况,程序就会崩溃
class Dog {
public:
string name;
Dog(string name) {this->name = name;
cout << name << " is born.\n";
}
~Dog() { cout << name << " is destroied.\n";
}void bark() { cout << name << " is barking.\n";
}
};
int main() {
try {
Dog dog1("Henry");
Dog dog2("Bob");
throw 20;
dog1.bark();
dog2.bark();
} catch (int e) {
cout << e << " is caught" << endl;
}
}/*
* output:
* Henry is born.
* Bob is born.
* Bog is destroied.
* Henry is destroied.
* 20 is caught
*/
从上面的例子可以看出,
catch
语句块在两个局部对象析构完成后才执行,意味着在异常被捕获之前,try
代码块中的栈需要被清理。我们把上面代码稍作修改,把
throw
语句放到析构函数中,看下会发生什么class Dog {
public:
string name;
Dog(string name) {this->name = name;
cout << name << " is born.\n";
}
~Dog() { cout << name << " is destroied.\n";
throw 20;
}void bark() { cout << name << " is barking.\n";
}
};
int main() {
try {
Dog dog1("Henry");
Dog dog2("Bob");
dog1.bark();
dog2.bark();
} catch (int e) {
cout << e << " is caught" << endl;
}
}/* Output:
* Henry is born.
* Bob is born.
* Henry is barking.
* Bob is barking.
* Bob is destroied.
* Henry is destroied.
* libc++abi.dylib: terminating with uncaught exception of type int
* [1]51549 abort./exception
*/
可以看到程序崩溃了,崩溃原因是
terminating with uncaught exception of type int
:异常没有被处理。我们来分析下其中的原因,在 try
中,我们定义了两个对象,并按照顺序调用了它们的 bark
接口,随后离开 try
代码块,此时,编译器会自动释放这两个局部对象,调用它们的析构函数,因为栈的特性是后进先出,所以先析构 Bob
,在执行 Bob
的析构函数时,抛出了异常,但此时并不会立即执行 catch
语句块,根据上文提到的第一条规则:「在 catch
执行前,需要先清理 try
中的堆栈」。于是 Henry
也被析构了,这让 try
语句块中抛出了 2 个异常,直接导致了程序的崩溃。找到上述程序崩溃的元凶后,我们便学到了一条宝贵的 C++ 经验:
不要在析构函数中抛出异常。因为如果你的析构函数中有异常抛出的话,你便无法控制
try
语句中抛出来的异常数量——这将是一场灾难。为了不在析构函数中抛出异常,一般有两种做法:
- 在析构函数内部捕获异常,防止异常被抛出,例如下面的代码
~Dog { try { // may throw exception } catch (MyException e) { // Catch exception } catch (...) { // 尽量不要使用 ... 来捕获异常 } }
虽然这样做,你的析构函数再不会抛出异常了,但却带来了一些隐患,即你使用了(...)
来捕获异常,这种代码没有任何用处(它无法输出有效的异常信息),同时由于它会捕获一切异常,于是会将一些必要的程序缺陷掩盖起来,而不是“尽早的暴露问题”。所以在这里,我们学到的第二条经验是:
不要使用
(...)
来捕获异常
- 保持析构函数简洁,将可能导致异常的代码移到其他的函数中。这才是推荐的做法。
- Advanced C++: Exceptions in Destructors
推荐阅读
- 急于表达——往往欲速则不达
- 2018-02-06第三天|2018-02-06第三天 不能再了,反思到位就差改变
- 家乡的那条小河
- 一个人的碎碎念
- 赠己诗
- 这辈子我们都不要再联系了
- 死结。
- 我从来不做坏事
- 时间老了
- 我错了,余生不再打扰