看這篇文章學習C++異常處理的基礎知識。看完后,還不過癮,為什么大家在C++代碼中都不用Exception?為什么C++11會引入一些變化?
為什么C++ exception handling需要unwind stack?
為什么throw會被拋棄?
接着看http://www.gotw.ca/publications/mill22.htm, 總結如下:
throw() specification 可以讓程序員在自己寫的函數上標注該函數是否會拋出異常, 會拋出哪些類型的異常,但是throw有如下的問題:
1. C++基於throw的異常處理部分是一個“Shadow Type System”
為什么這么說呢?throw()語法有時被認為是函數簽名的一部分,而有的時候卻不允許,而這沒有標准規定,比如下面的例子
// Example 2(a): You can’t write an ES // in a typedef. // void f() throw(A,B); typedef void (*PF)() throw(A,B); // syntax error PF pf = f; // can’t get here
在typdef時不把throw作為函數類型的一部分,但看下面的例子:
// Example 2(b): But you can if you omit // the typedef! // void f() throw(A,B); void (*pf)() throw(A,B); // ok pf = f; // ok
還有函數指針的例子:
// Example 2(c): Also kosher, low-carb, // and fat-free. // void f() throw(A,B); void (*pf)() throw(A,B,C); // ok pf = f; // ok, less restrictive
還有繼承的virtual 函數的例子:
// Example 2(d): And the ES in the signature // does matter if it’s a virtual function. // class C { virtual void f() throw(A,B); // same ES }; class D : C { void f(); // error, now the ES matters };
2. 對throw語法的(錯誤)理解
很多人認為throw表示下面的意思:
1. Guarantee that functions will only throw listed exceptions (possibly none).
2. Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrown.
對第一條,看下面的代碼:
// Example 1(b) reprise, and two // potential white lies: // int Gunc() throw(); // will throw nothing (?) int Hunc() throw(A,B); // can only throw A or B (?)
Gunc()真的不會throw任何異常嗎?Hunc()真的只會拋出類型A和B的異常嗎?不是的,代碼復雜、調用嵌套多次后,很多時候程序員是無法准確標注這個函數會throw什么樣的exception。而一旦未被標注的異常發生后,編譯器也只是默默地做點事情,而這對我們的程序沒有什么幫助。如果一個未被標准的異常發生后,編譯器就調用std::unexpected()函數。unexpected()函數是全局的,很難對特定的exception提供很有幫助的處理,大部分情況就直接terminate,而且unexpected()函數是不會返回的,所以,這樣的異常一旦發生,就等於退出程序。
對throw的理解應該換成下面的兩句:
- Guarantee Enforce at runtime that functions will only throw listed exceptions (possibly none).
- Enable or prevent compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrown having to check whether listed exceptions are indeed being thrown.
下面看看編譯器干了什么,對下面的代碼:
// Example 3(a) // int Hunc() throw(A,B) { return Junc(); }
編譯器生成如下代碼:
// Example 3(b): A compiler’s massaged // version of Example 3(a) // int Hunc() try { return Junc(); } catch( A ) { throw; } catch( B ) { throw; } catch( ... ) { std::unexpected(); // will not return! but } // might throw an A or a B if you’re lucky
可見,編譯器並不是根據listed exception做優化,編譯器需要生成更多的代碼來保證在運行時只有listed exception被throw出來,而沒有list就調用unexpected函數了。
在回頭看對throw的正確的理解:
1. 保證運行時,只會throw listed exception,而如果發生不listed 的exception,那就調用unexpected;
2. 允許或者禁止編譯器不得不進行是否listed exception發生的檢查。
在上面說明了使用throw時,編譯器需要生成try-catch代碼, 其實throw還有其他問題:1. 有些編譯器會自動拒絕為使用throw的function做inline優化;2. 有些編譯器不回去對與exception有關的知識進行優化,即使有些代碼絕不會throw exception,但編譯器還是會生成try-catch代碼。3. throw在virtual函數中時signature的一部分,所以,如果你把base class 的virtual方法中throw的類型之一或者若干個去掉了,那就也需要更新子類的代碼,這樣實際上增加了coupling,是很不好的設計。
所以關於throw,建議是
Moral #1: Never write an exception specification.
Moral #2: Except possibly an empty one, but if I were you I’d avoid even that.
現在大家都做cross-platform的開發,多一事不如少一事,throw不會帶來什么好處,所以,就完全不要用了。
既然throw有如此多的問題,那C++11帶來了什么呢?
Reference:
1. http://www.learncpp.com/cpp-tutorial/153-exceptions-functions-and-stack-unwinding/
2. http://www.gotw.ca/publications/mill22.htm
3. http://stackoverflow.com/questions/88573/should-i-use-an-exception-specifier-in-c/88905#88905
4. http://stackoverflow.com/questions/10787766/when-should-i-really-use-noexcept