C++ 對待異常處理有兩個(gè)規(guī)則
- 如果在
try...catch
中有異常拋出,則在catch
執(zhí)行前,會(huì)先將try
語句塊對應(yīng)的棧清空 - C++ 不允許在同一個(gè)
try...catch
中處理1個(gè)以上的異常挚歧,如果發(fā)生此種情況芒涡,程序就會(huì)崩潰
為便于理解摩梧,我們先來看一個(gè)例子
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
語句塊在兩個(gè)局部對象析構(gòu)完成后才執(zhí)行,意味著在異常被捕獲之前仅父,try
代碼塊中的棧需要被清理叛薯。
我們把上面代碼稍作修改,把 throw
語句放到析構(gòu)函數(shù)中笙纤,看下會(huì)發(fā)生什么
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
中强霎,我們定義了兩個(gè)對象忿项,并按照順序調(diào)用了它們的 bark
接口蓉冈,隨后離開 try
代碼塊,此時(shí)轩触,編譯器會(huì)自動(dòng)釋放這兩個(gè)局部對象寞酿,調(diào)用它們的析構(gòu)函數(shù),因?yàn)闂5奶匦允呛筮M(jìn)先出脱柱,所以先析構(gòu) Bob
伐弹,在執(zhí)行 Bob
的析構(gòu)函數(shù)時(shí),拋出了異常榨为,但此時(shí)并不會(huì)立即執(zhí)行 catch
語句塊惨好,根據(jù)上文提到的第一條規(guī)則:「在 catch
執(zhí)行前,需要先清理 try
中的堆椝婀耄」日川。于是 Henry
也被析構(gòu)了,這讓 try
語句塊中拋出了 2 個(gè)異常矩乐,直接導(dǎo)致了程序的崩潰龄句。
找到上述程序崩潰的元兇后回论,我們便學(xué)到了一條寶貴的 C++ 經(jīng)驗(yàn):
不要在析構(gòu)函數(shù)中拋出異常。
因?yàn)槿绻愕奈鰳?gòu)函數(shù)中有異常拋出的話分歇,你便無法控制 try
語句中拋出來的異常數(shù)量——這將是一場災(zāi)難傀蓉。
為了不在析構(gòu)函數(shù)中拋出異常,一般有兩種做法:
-
在析構(gòu)函數(shù)內(nèi)部捕獲異常职抡,防止異常被拋出葬燎,例如下面的代碼
~Dog { try { // may throw exception } catch (MyException e) { // Catch exception } catch (...) { // 盡量不要使用 ... 來捕獲異常 } }
雖然這樣做,你的析構(gòu)函數(shù)再不會(huì)拋出異常了繁调,但卻帶來了一些隱患萨蚕,即你使用了
(...)
來捕獲異常,這種代碼沒有任何用處(它無法輸出有效的異常信息)蹄胰,同時(shí)由于它會(huì)捕獲一切異常岳遥,于是會(huì)將一些必要的程序缺陷掩蓋起來,而不是“盡早的暴露問題”裕寨。所以在這里浩蓉,我們學(xué)到的第二條經(jīng)驗(yàn)是:不要使用
(...)
來捕獲異常 保持析構(gòu)函數(shù)簡潔,將可能導(dǎo)致異常的代碼移到其他的函數(shù)中宾袜。這才是推薦的做法捻艳。
參考