C與C++中的異常處理3 (轉)

worldblog發表於2007-12-12
C與C++中的異常處理3 (轉)[@more@]

C++異常處理的基本語法和語義:namespace prefix = o ns = "urn:schemas--com::office" />

  這次,我來概述標準C++異常處理的基本語法和語義。順便,我會將它和前兩次提到的技術進行比較。(在本文及以後,我將標準C++異常處理簡稱為EH,將的方法稱為SEH。)

1.1  基本語法和語義

  EH引入了3個新的C++語言關鍵字:

l  catch

l  throw

l  try

  異常透過如下語句觸發

throw [expression]

  透過“異常規格申明”定義它將丟擲什麼異常:

throw([type-ID-list])

  可選項type-ID-list包含一個或多個型別的名字,以逗號分隔。這些異常靠try塊中的異常處理函式進行捕獲。

try compound-statement handler-sequence

  處理函式佇列包含一個或多個處理函式,形式如下:

catch ( exception-declaration ) compound-statement

  處理函式的“異常申明”指明瞭這個函式將捕獲什麼型別的異常。

  和SEH一樣,跟在try和catch後面的語句必須刮在{}內,而整個try塊組成一條完整的大語句。

  例子:

void f() throw(int, some_class_type)

  {

  int i;

  // ... generate an 'int' exception

  throw i;

  // ...

  }

 

int main()

  {

  try

  {

  f();

  }

  catch(int e)

  {

  // ... handle 'int' exception ...

  }

  catch(some_class_type e)

  {

  // ... handle 'some_class_type' exception ...

  }

  // ... possibly other handlers ...

  return 0;

  }

 

  異常規格申明是EH特有的,SEH和MFC都沒有類似的東西。一個空的異常規格申明表明函式不丟擲任何異常:

void f() throw()

  {

  // ... function throws no exceptions ...

  }

 

  如果函式沒有異常規格申明,它可以丟擲任何型別的異常:

void f()

  {

  // ... function can throw anything or nothing ...

  }

 

  當函式拋異常時,關鍵字throw通常後面帶一個被丟擲的:

throw i;

 

  然而,throw也可以不帶物件:

catch(int e)

  {

  // ... handle 'int' exception ...

  throw;

  }

 

  它的效果是再次丟擲當前正被捕獲的物件(int  e)。因為空throw的作用是再次丟擲已存在的異常物件,所以它必須位於catch語句塊中。MFC也有再次丟擲異常的功能,SEH則沒有,它沒有將異常物件交給過處理函式,所以沒什麼可再次丟擲的。

  就象函式原型中的引數申明一樣,異常申明也可以是無名的:

catch(char *)

  {

  // ... handle 'char *' exception ...

  }

  當這個處理函式捕獲一個char *型的異常物件時,它不能操作這個物件,因為這個物件沒有名字。

  異常申明還可以是這樣的特殊形式:

catch(...)

  {

  // ... handle any type of exception ...

  }

 

  就象不定引數中的“...”一樣,異常申明中的“...”可以匹配任何異常的型別。

1.2  標準異常物件的型別

  標準庫函式可能報告錯誤。在C標準庫中的報錯方式在前面說過了。在C++標準庫中,有些函式丟擲特定的異常,而另外一些根本不拋任何異常。

  因為C++標準中沒有明確規定,所以C++的庫函式可以丟擲任何物件或不拋。但C++標準推薦執行庫的實現透過丟擲定義在中的異常型別或其派生型別來報告錯誤:

namespace std

  {

  class logic_error;  // : public exception

  class ain_error;  // : public logic_error

  class invalid_argument; // : public logic_error

  class length_error;  // : public logic_error

  class out_of_range;  // : public logic_error

  class runtime_error;  // : public exception

   class range_error;  // : public runtime_error

  class overflow_error;  // : public runtime_error

  class underflow_error;  // : public runtime_error

  }

 

  這些(異常)類只對C++標準庫有力。在你自己的程式碼中,你可以丟擲(和捕獲)任何你所象要的型別。 

1.3  標準中的其它申明

  標準庫頭申明瞭幾個EH型別和函式

namespace std

  {

  //

  // types

  //

  class bad_exception;

  class exception;

  typedef void (*tenate_handler)();

  typedef void (*unexpected_handler)();

  //

  // functions

  //

  terminate_handler set_terminate(terminate_handler) throw();

  unexpected_handler set_unexpected(unexpected_handler) throw();

  void terminate();

  void unexpected();

  bool uncaught_exception();

  }

 

  提要:

l  exception是所有標準庫丟擲的異常的基類。

l  uncaught_exception()函式在有異常被丟擲卻沒有被捕獲時返回true,其它情況返回false。它類似於SEH的函式AbnormalTermination()。

l  terminate()是EH的應急處理。它在異常處理體系陷入了不可恢復狀態時被,經常是因為試圖重入(在前一個異常正處理過程中又拋了一個異常)。

l  unexpected()在函式丟擲一個它沒有在“異常規格申明”中申明的異常時被呼叫。這個預料外的異常可能在退棧過程中被替換為一個bad_excetion物件。

l  執行庫提供了預設terminate_handler()和unexpected_handler() 函式處理對應的情況。你可以透過set_terminate()和set_unexpected()函式替換庫的預設版本。

 

1.4  異常生命期

  EH執行於異常生命期的五個階段:

l  或執行庫遇到一個錯誤狀況(階段1)並且丟擲一個異常(階段2)。

l  程式的執行停止於異常點,開始搜尋異常處理函式。搜尋沿呼叫棧向上搜尋(很象SEH終止異常時的行為)。

l  搜尋結束於找到了一個異常申明與異常物件的靜態型別相匹配(階段3)。於是進入相應的異常處理函式。

l  異常處理函式結束後,跳到此異常處理函式所在的try塊下面最近的一條語句開始(階段5)。這個行為意味著C++標準中異常總是終止。

  這些步驟演示於這個簡單的例子中:

#include

static void f(int n)

  {

  if (n != 0) // Stage 1

  throw 123; // Stage 2

  }

extern int main()

  {

  try

  {

  f(1);

  printf("resuming, should never appearn");

  }

  catch(int) // Stage 3

  {

  // Stage 4

  printf("caught 'int' exceptionn");

  }

  catch(char *) // Stage 3

  {

  // Stage 4

  printf("caught 'char *' exceptionn");

  }

  catch(...) // Stage 3

  {

  // Stage 4

  printf("caught typeless exceptionn");

  }

  // Stage 5

  printf("terminating, after 'try' blockn");

  return 0;

  }

/*

  When run yields

  caught 'int' exception

  terminating, after 'try' block

*/

 

1.5  基本原理

  C標準庫的異常體系處理C++語言時有如下難題:

l  解構函式被忽略。既然C標準庫異常體系是為C語言設計的,它們不知道C++的解構函式。尤其,abort()、exit()和longjmp()在退棧或程式終止時不呼叫區域性物件的解構函式。

l  繁瑣的。查詢全域性物件或函式返回值導致了程式碼混亂-你必須在所有可能發生異常的地方進行明確的異常情況檢測,即使是異常情況可能實際上從不發生。因為這種方法是如此繁瑣,程式設計師們可能會故意“忘了”檢測異常情況。

l  無彈性的。Longjmp()“丟擲”的只能是簡單的int型。errno和signal()/raise()只使用了很小的一個值域集合,解析度很低。Abort()和exit()總是終止程式。Assert()只工作在de版本中。

l  非固有的。所有的C標準庫異常體系都需要執行庫的支援,它不是語言核心支援的。

 

  微軟特有的異常處理體系也不是沒有限制的:

l  SEH異常處理函式不是直接捕獲一個異常物件,而是透過查詢一個(概念性的)類似errno的全域性值來判斷什麼異常發生了。

l  SEH異常處理函式不能組合,給定try塊的唯有的一個處理函式必須在執行期識別和處理所有的異常事件。

l  MFC異常處理函式只能捕獲CException及派生型別的指標。

l  透過包含定義了MFC異常處理函式的宏的標頭檔案,程式包含了數百個無關的宏和申明。

l  MFC和SEH都是專屬於與Microsoft相容的開發環境和執行平臺的。

 

  標準C++異常處理避免了這些短處:

l  析構。在拋異常而進行退棧時,區域性物件的解構函式被按正確的順序呼叫。

l  不引人注目的。異常的捕獲是暗地裡的和自動的。程式設計師無需因錯誤檢測而搞亂設計。

l  精確的。因為幾乎任何物件都可以被丟擲和捕獲,程式設計師可以控制異常的內容和含義。

l  可伸縮的。每個函式可以有多個try塊。每個try塊可以有單個或一組處理函式。每個處理函式可以捕獲單個型別,一組型別或所有型別的異常。

l  可預測的。函式可以指定它們將拋的異常型別,異常處理函式可以指定它們捕獲什麼型別的異常。如果程式違反了其申明,標準庫將按可預測的、定義的方式執行。

l  固有的。EH是C++語言的一部分。你可以定義、throw和catch異常而不需要包含任何庫。

l  標準的。EH在所有的標準C++的實現中都可用。

  基於更完備的想法,C++標準委員會考慮過兩個EH的設計,在D&E的16章。(For a more complete rationale, including alternative EH designs consred by the C++ Standard's committee, check out Chapter 16 of the D&E.)

1.6  小結

  下次,我將更深入挖掘EH的語言核心特性和EH的標準庫支援。我也將展示Microsoft Visual C++實現EH的內幕。我將開始標誌出EH的那些Visual C++只部分支援或完全不支援的特性,並且尋找繞過這些限制的方法。

在我相信設計EH的基本原理是健全的的同時,我也認為EH無意中包含了一些嚴重的後果。不用責備C++標準的制訂者的短視,我理解設計和實現有效的異常處理是多麼的難。當我們遭遇到這些無意中的後果時,我將展示它們對你程式碼的微妙影響,並且推薦一些技巧來減輕其影響。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992208/,如需轉載,請註明出處,否則將追究法律責任。

相關文章