C++的異常處理之一:throw是個一無是處的東西


這篇文章學習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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM