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

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

1.1  版本3:恢復異常:namespace prefix = o ns = "urn:schemas--com::office" />

  接下來,改:

 __except(except_filter(3, EXCEPTION_CONTINUE_SEARCH))

 

為:

__except(except_filter(3, EXCEPTION_CONTINUE_EXECUTION))

 

  重新編譯並執行。可以看到這樣的輸出:

0:before first try

1:  try

2:  try

3:  try

4:  try

4:  raising exception

3:  filter => EXCEPTION_CONTINUE_EXECUTION

4:  after exception

4:  handling normal tenation

3:  continuation

2:  continuation

2:  handling normal termination

1:  continuation

0:continuation

 

  因為第三層的異常過濾器已經捕獲了異常,第一層的過濾器不會被求值。捕獲異常的過濾器求值為EXCEPTION_CONTINUE_EXECUTION,因此異常被恢復。異常處理不會被進入,將從異常發生點正常下去。

 

1.2  版本4:異常終止

  這樣的結構:

__try

  {

  /* ... */

  return;

  }

 

或:

__try

  {

  /* ... */

  goto label;

  }

__finally

  {

  /* ... */

  }

/* ... */

label:

 

被認為是try塊異常終止。以後AbnormalTermination()函式的話將返回非0值,就象異常仍然存在。

  要看其效果,改這兩行:

trace(4, "raising exception");

RaiseException(exception_code, 0, 0, 0);

 

為:

trace(4, "exiting try block");

goto end_4;

 

  第4層的try塊不是被一個異常結束的,現在是被goto語句結束的。執行結果:

0:before first try

1:  try

2:  try

3:  try

4:  try

4:  exiting try block

4:  handling abnormal termination

3:   continuation

2:  continuation

2:  handling normal termination

1:  continuation

0:continuation

  第4層的終止處理函式認為它正在處理異常終止,雖然並沒有發生過異常。(如果發生過異常的話,我們至少能從一個異常過濾器的輸出資訊上看出來的。)

  結論:你不能只依賴AbnormalTermination()函式來判斷異常是否仍存在。

 

1.3  版本5:正常終止

  如果想正常終止一個try塊,也就是想要AbnormalTermination() 函式返回FALSE,應該使用Microsoft特有的關鍵字__leave。想驗證的話,改:

goto end_4;

 

為:

__leave;

 

  重新編譯並執行,結果是:

0:before first try

1:  try

2:  try

3:  try

4:  try

4:  exiting try block

4:  handling normal termination

3:  continuation

2:  continuation

2:  handling normal termination

1:  continuation

0:continuation

 

  和版本4的輸出非常接近,除了一點:第4層的終止處理函式現在認為它是在處理正常結束。

1.4  版本6:隱式異常

  前面的版本處理的都是產生的異常。SEH也可以處理自己丟擲的異常。

  改這行:

trace(4, "exiting try block");

__leave;

 

為:

trace(4, "implicitly raising exception");

*((char *) 0) = 'x';

 

  這導致Windows的操作異常(引用空指標)。接著改:

__except(except_filter(3, EXCEPTION_CONTINUE_EXECUTION))

 

為:

__except(except_filter(3, EXCEPTION_EXECUTE_HANDLER))

 

以使程式捕獲並處理異常。

  執行結果為:

0:before first try

1:  try

2:  try

3:  try

4:  try

4:  implicitly raising exception

3:  filter => EXCEPTION_EXECUTE_HANDLER

4:  handling abnormal termination

3:  handling exception

2:  continuation

2:  handling normal termination

1:  continuation

0:continuation

 

  如我們所預料,Windows在巢狀層次4中觸發了一個異常,並被層次3的異常處理函式捕獲。

  如果你想知道捕獲的精確異常碼,可以讓異常傳到main外面去,就象版本2中做的。為此,改:

__except(except_filter(3, EXCEPTION_EXECUTE_HANDLER))

為:

__except(except_filter(3, EXCEPTION_CONTINUE_SEARCH))

 

  結果對話方塊在按了“Details”後,顯示的資訊非常象使用者異常。

 

圖3  記憶體異常對話方塊

  和版本2的對話方塊不同是,上次顯示了特別的異常碼,這次說了“invalid page fault”--更使用者友好些吧。

1.5  C++考慮事項

  在所有C相容異常處理體系中,SEH無疑是最完善和最靈活的(至少在Windows環境下)。具有諷刺意味的,它也是Windows體系以外的環境中最不靈活的,它將你和特殊的執行平臺及Visaul C++原始碼相容的牢牢綁在了一起。

  如果只使用C語言,並且不考慮移植到Windows平臺以外,SEH很好。但如果使用C++並考慮可移植性,我強烈建議你使用標準C++異常處理而不用SEH。你可以在同一個程式中同時使用SEH和標準C++異常處理,只有一個限制:如果在有SEH try塊的函式中定義了一個,而這個物件又沒有non-trivial(無行為的)解構函式,編譯器會報錯。在同一函式中同時使用這樣的物件和SEH的__try,你必須禁掉標準C++異常處理。

  (Visual C++預設關掉標準C++異常處理。你可以使用命令列引數/GX或的Project Settings對話方塊開啟它。)

  在以後的文章中,我會在討論標準C++異常處理時回顧SEH。我想將SEH整合入C++的主流中,透過將結構化異常及Windows執行庫支援對映為C++異常和標準C++執行庫支援。

1.6  MFC異常處理

  說明:這一節我需要預先引用一點點標準C++異常處理的知識,但要到下次才正式介紹它們。這個提前引用是不可避免的,也是沒什麼可驚訝的,因為Microsoft將它們的MFC異常的語法和語義構建在標準C++異常的語法和語義的基礎上。

  我到現在為止所講的異常處理方法對C和C++都有效。在此之外,Microsoft對C++程式還有一個解決方案:MFC異常處理類和宏。Microsoft現在認為MFC異常處理體系過時了,並鼓勵你儘可能使用標準C++異常處理。然而Visual C++仍然支援MFC異常類和及宏,所以我將給它個簡單介紹。

  Microsoft用標準C++異常實現了MFC3.0及以後版本。所以你必須啟用標準C++異常才能使用MFC,即使你不打算顯式地使用這些異常。前面說過,你必須禁掉標準C++異常來使用SEH,這也意味著你不能同時使用MFC宏和SEH。Microsoft明文規定這兩個異常體系是互斥的,不能在同一程式中混合使用。

   SEH是擴充套件了編譯器關鍵字集,MFC則定義了一組宏:

l  TRY

l  CATCH, AND_CATCH, 和END_CATCH

l  THROW 和 THROW_LAST

  這些宏非常象C++的異常關鍵字try、catch和throw。

  另外,MFC提供了異常類體系。所有名字為CXXXException形式的類都是從抽象類CException派生的。這類似於標準C++執行庫在中申明的從std::exception開始的派生體系。但,標準C++的關鍵字可以處理絕大部分型別的異常物件,而MFC宏只能處理CException的派生型別物件。

  對於每個MFC異常類CXXXException,都有一個全域性的輔助函式AfxThrowXXXException() ,它構造、初始化和丟擲這個類的物件。你可以用這些輔助函式處理預定義的異常型別,用THROW處理自定義的物件(當然,它們必須是從CException派生的)。

  基本的設計原則是:

l  用TRY塊包含可能產生異常的程式碼。

l  用CATCH檢測並處理異常。異常處理函式並不是真的捕獲物件,它們其實是捕獲了指向異常的指標。MFC靠動態型別來辨別異常物件。比較一下,SEH靠執行時查詢異常碼來辨別異常。

l  可以在一個TRY塊上捆綁多個異常處理函式,每個捕獲一個C++靜態型別不同的物件。第一個處理函式使用宏CATCH,以後的使用AND_CATCH,用END_CATCH結束處理函式佇列。

l  MFC自己可能觸發異常,你也可以顯式觸發異常(透過THROW或MFC輔助函式)。在異常處理函式內部,可以用THROW_LAST再次丟擲最近一次捕獲的異常。

l  異常一被觸發,異常處理函式就將被從裡到外進行搜尋,和SEH時一樣。搜尋停止於找到一個型別匹配的異常處理函式。所有異常都是終止。和SEH不一樣,MFC沒有終止處理函式,你必須依賴於區域性物件的解構函式。

  一個小MFC例子,將大部分題目都包括了:

#include

#include "afxwin.h"

void f()

  {

  TRY

  {

  printf("raising memory exceptionn");

  AfxThrowMemoryException();

  printf("this line should never appearn");

  }

  CATCH(CException, e)

  {

  printf("caught generic exception; rethrowingn");

  THROW_LAST();

  printf("this line should never appearn");

  }

  END_CATCH

  printf("this line should never appearn");

  }

int main()

  {

  TRY

  {

  f();

  printf("this line should never appearn");

  }

  CATCH(CFileException, e)

  {

  printf("caught file exceptionn");

  }

  AND_CATCH(CMemoryException, e)

  {

  printf("caught memory exceptionn");

  }

 /* ... handlers for other CException-derived types ... */

  AND_CATCH(CException, e)

  {

  printf("caught generic exceptionn");

  }

  END_CATCH

  return 0;

  }

/*

  When run yields

  raising memory exception

  caught generic exception; rethrowing

  caught memory exception

*/

 

  記住,異常處理函式捕獲指向物件的指標,而不是實際的物件。所以,處理函式:

CATCH(CException, e)

  {

  // ...

  }

 

定義了一個區域性指標CException *e指向了被丟擲的異常物件。基於C++的多型,這個指標可以引用任何從CException派生的物件。

  如果同一try塊有多個處理函式,它們按從上到下的順序進行匹配搜尋的。所以,你應該將處理最派生類的物件的處理函式放在前面,不然的話,更派生類的處理函式不會接收任何異常的(再次拜多型所賜)。

  因為你典型地想捕獲CException,MFC定義了幾個CException特有宏:

l  CATCH_ALL(e)和AND_CATCH_ALL(e),等價於CATCH(CException, e)和AND_CATCH(CException, e)。

l  END_CATCH_ALL ,結束CATCH_ALL... AND_CATCH_ALL佇列。

l  END_TRY等價於CATCH_ALL(e);END_CATCH_ALL。這讓TRY... END_TRY中沒有處理函式或說是接收所有丟擲的異常。

  這個被指的異常物件由MFC隱式析構和歸還記憶體。這一點和標準C++異常處理函式不一樣,MFC異常處理不會讓任何人取得被捕獲的指標的所有權。因此,你不能用MFC和標準C++體系同時處理相同的異常物件;不然的話,將導致記憶體洩漏:引用已被析構的物件,並重復析構和歸還同一物件。

1.7  小結

  MSDN線上還有另外幾篇探索結構化異常處理和MFC異常宏的文章。

下次我將介紹標準C++異常,概述它們的特點及基本原理。我還會將它們和到現在已經看到的方法進行比較。


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

相關文章