C++ Exception 筆記

1. 在操作 exception 時,有三種情況會讓程式 terminate()。

1.1 重覆 throw exception 。

一般在 throw exception 時,程式會一路退出 stack 中的函式,直到找到 catch ,又稱 unwinding stack。當離開函式的範圍時,在 stack 的 auto 物件就會被消滅,而執行物件的 destructor。但是不好的設計會讓 destructor 又丟出 exception ,造成當有一個 exception 還沒被 catch 住,又遭遇到了第二個 exception ,C++ 標準沒有對此情況有定義,一般的 compiler 可能會直接執行 terminate() 來直接結束程式。

結論: 不要在 destructor 中,丟出 exception。 [C++ Primer ch 17.1], [Effective C++ Rule8], [C++ Coding Standards Rule 51]

1.2 丟出的 exception 沒有人 catch。

因為 exception 一丟出,控制流程會一路退退退到最外層去尋找 catch,所以程式結束是很正常的。

1.3 在一個不是 catch block 中,做 rethrow。

Rethrow
}catch (runtime_error& e){ 
  log_something(e.what());
  ... 
  throw;
} 

你在處理完一個 exception 時,想再讓這個 exception 再丟給再外一層的 catch 去做處理時,就可以使用 re-throw。也就是 throw 後面不加東西丟出,它會把 catch 到的 e 給再丟一次。所以如果你不是在 catch 中 re-throw,程式會無法得知目前的 exception 是什麼而 terminate();。

2. 不要丟出使用指標的 exception 。

因為 exception 的機制會不知道它的動態型別。

class runtime_error{...};
class arithmetic_error : public runtime_error{...};
class plus_error : public arithmetic_error{...};

try{
  arithmetic_error *p = new plus_error(); 
  throw *p;
}catch(runtime_error& r){
  ... //  compiler 只知道 r 是 arithmetic_error,只能夠做靜態多型,也就是只知道寫死在 try 裡 arithmetic_error 型別。
}

3. 盡量使用 catch by reference ,不要使用 call by value 。

因為 call by value 沒有多型,遇到多型的物件,會被 trunk 掉。

4. 除了 non-throw 保證外,不要使用 Exception Specification (ES)。

exception specification
void func() throw(runtime_error, bad_cast); // 宣稱說,我 func 會丟出 runtime_error, bad_cast 這兩種。
void gunc() throw(); // 宣稱說,我 gunc() 不會丟出任何 exception 。
}

4.1 缺點: 浪費效能。

就算你寫了只 throw(A, B),程式也可是有可能會丟 C 。而且 compiler 還會為了 ES 加入許多 try/catch 來 runtime check 你的 exception 是不是正確的,如果不正確也就只是結束程式,根本浪費效能。

4.2 缺點: 增加 coupling 。

因為 derived virtual function 的 ES 必须更 narrower base virtual function ,哪天你更動了 base virtual function 的 ES,就等著加班吧。

4.3 缺點: non-throw 相依性。

上禮拜的 code
void Func() throw(){ ... };
void Gunc() throw(){
  ...
  Func();
}
void Hunc() throw(){
  Gunc();
}
今天你讓 Func() 變得可能丟出 bad_alloc 這個 exception ,那 Gunc, 跟 Hunc 怎麼辦
void Func() throw(bad_alloc){
 int *a = new int(0);
}
void Gunc() throw(bad_alloc){ ... } // ok
void Hunc() throw(){ ... } // oops! 忘了改

4.4 缺點:

standard 是一回事,compiler 實作是另一回事,有時 ES 的保證語意會依照實作不同而有所變動,像是有時候 MSVC 就是當 throw 出一個不在 ES 的 exception ,會假裝沒發生過。

4.5 唯一優點:

你標出 throw(); 表示不會丟 exception ,來表示你的 func 是最高等級的 exceptional safety ,別人可以很放心用,就像 std library 裡的東西一樣。

C++ 11 中已經改良成 noexcept keyword 來取代 throw();。若有 exception 還是被丟出的話,前者會直接執行 terminal();後者是執行 unexpected();,這是其中一個不同之處。

4.6 StackOverflow 上的小道消息:

ES 似乎不是 Bjarne Stroustrup 想加的,是委員會某個有力人士堅持不加就不簽,後來 C++11 才被 deprecated。

Reference

http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=483
http://www.gotw.ca/publications/mill22.htm
http://programmers.stackexchange.com/questions/114338/why-are-exception-specifications-bad

5. rethrow 的 exception instance。

catch by reference 時,當有更動 exception instance 時,rethrow 是會傳遞已被更動的 exception instance 。
但是若是 catch by value ,rethrow 則是會傳遞 exception instance 的複本。

6. 不要用 SEH,Structured Exception Handling (微軟特產)。

它是非同步的,它會阻止 compiler 對 __try/__catch 裡 code 做 optimization。
http://blogs.msdn.com/b/larryosterman/archive/2004/09/10/228068.aspx

comments powered by Disqus