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

gugu99發表於2008-08-06
C與C++中的異常處理12 (轉)[@more@]

:namespace prefix = o ns = "urn:schemas--com::office" />

  上次,我介紹了C++標準執行庫unexpected(),並展示了Visual C++的實現版本中的限制。這次,我想展示所有unexpected()的實現上固有的限制,以及繞開它們的辦法。

 

1.1  異常處理函式是全域性的、通用的

  我在上次簡要地提過這點,再推廣一點:過濾unexpected異常的異常處理函式unexpected()是全域性的,對每個是唯一的。

  所有unexpected異常都被同樣的一個unexpected()異常處理函式處理。標準執行庫提供預設的處理函式來處理所有unexpected異常。你可以用自己的版本覆蓋它,這時,執行庫會你提供的處理函式來處理所有的unexpected異常。

  和普通的異常處理函式,如:

catch (int)

  {

  }

不同,unexpected異常處理函式不“捕獲”異常。一旦被進入,它就知道有unexpected異常被丟擲,但不知道型別和起因,甚至沒法得到執行庫的幫助:執行庫中沒有程式或儲存這些討厭的異常。

  在最好的情況下,unexpected異常處理函式可以把控制權交給程式的其它部分,也許它們有更好的辦法。例如:

#include

using namespace std;

 

void my_unexpected_handler()

  {

  throw 1;

  }

 

void f() throw(int)

  {

  throw 1L; // s -- *bad* function

  }

 

int main()

  {

  set_unexpected(my_unexpected_handler);

  try

  {

  f();

  }

  catch (...)

  {

  }

  return 0;

  }

   f()丟擲了一個它承諾不拋的異常,於是my_unexpected_handler()被呼叫。這個處理函式沒有任何辦法來判斷它被進入的原因。除了結束程式外,它唯一可能有些用的辦法是丟擲另外一個異常,希望新異常滿足被老異常違背的異常規格申明,並且程式的其它部分將捕獲這個新異常。

  在這個例子裡,my_unexpected_handler()丟擲的int異常滿足老異常違背的異常規格申明,並且main()成功地捕獲了它。但稍作變化:

#include

using namespace std;

 

void my_unexpected_handler()

  {

  throw 1;

  }

 

void f() throw(char)

  {

  throw 1L; // oops -- *bad* function

  }

 

int main()

  {

  set_unexpected(my_unexepected_handler);

  try

  {

  f();

  }

  catch (...)

  {

  }

   return 0;

  }

  my_unexpected_handler()仍然在unexpected異常發生後被呼叫,並仍然丟擲了一個int型異常。不幸的是,int型異常現在和老異常違背的異常規格申明相違背。因此,我們現在兩次違背了同一異常規格申明:第一次是f(),第二次是f()的援助者my_unexpected_handler()。

 

1.2  Tenate

  現在,程式放棄了,並呼叫執行庫的程式terminate()自毀。terminate()函式是標準執行庫在異常處理上的最後一道防線。當程式的異常處理體系感到無望時,C++標準要求程式呼叫terminate()函式。C++標準的Subclause 15.5.1列出了呼叫terminate()的情況:

  和unexpected()處理函式一樣,terminate()處理函式也可以定義。但和unexpected()處理函式不同的是,terminate()處理函式必須結束程式。記住:當你的terminate()處理函式被進入時,異常處理體系已經無效了,此是程式所需要的最後一件事是找一個terminate()處理函式來丟棄異常。

  在能避免時就不要讓你的程式呼叫terminate()。terminate()其實是個叫得好聽點的exit()。如果terminate()被呼叫了,你的程式就會以一種不愉快的方式死亡。

  就如同不能完全支援unexpected()一樣,Visual c++也不能完全支援terminate()。要在實際執行中驗證的話,執行:

 

#include

#include

#include

using namespace std;

 

void my_terminate_handler()

  {

  printf("in my_terminate_handlern");

  abort();

  }

 

int main()

  {

  set_terminate(my_terminate_handler);

  throw 1; // nobody catches this

  return 0;

  }

 

  根據C++標準,丟擲了一個沒人捕獲的異常將導致呼叫terminate()(這是我前面提到的Subclause 15.5.1中列舉的情況之一)。於是,上面的程式一個輸出:

in my_terminate_handler

  但,用Visual C++編譯並執行,程式沒有輸出任何東西。

 

1.3  避免terminate

  在我們的unexpected()例子中,terminate()最終被呼叫是因為f()丟擲了unexpected異常。我們的unexpected_handler()試圖阻住這個不愉快的事,透過丟擲一個新異常,但沒成功;這個丟擲行為因再度產生它試圖解決的那個問題而結束。我們需要找到一個方法以使得unexpected()處理函式將控制權傳給程式的其它部分(假定那部分程式是足夠聰明的,能夠成功處掉異常)而不導致程式終止。

  很高興,C++標準正好提供了這樣一個方法。如我們所看過的,從unexpected()處理函式中丟擲的異常物件必須符合(老異常違背的)異常規格申明。這個規則有一個例外:如果如果被違背的異常規格申明中包含型別bad_exception,一個bad_exception物件將替代unexpected()處理函式丟擲的物件。例如:

#include

#include

using namespace std;

void my_unexpected_handler()

  {

  throw 1;

  }

 

void f() throw(char, bad_exception)

  {

  throw 1L; // oops -- *bad* function

  }

 

int main()

  {

  set_unexpected(my_unexpected_handler);

  try

  {

  f();

  }

  catch (bad_exception const &)

  {

  printf("caught bad_exceptionn");

  // ... even though such an exception was never thrown

  }

  return 0;

  }

 

  當用C++標準相容的編譯並執行,程式輸出:

caught bad_exception

  當用Visual C++編譯並執行,程式沒輸出任何東西。因為Visual c++並沒有在第一次拋異常的地方捕獲unexpected異常,它沒有機會進行bad_exception的替換。

  和前面的例子相同的是,f()仍然違背它的異常規格申明,而my_unexpected_handler()仍然丟擲一個int。不同之處是:f()的異常規格申明包含bad_exception。結果,程式悄悄地將my_unexpected_handler()原來丟擲的int物件替換為bad_exception物件。因為bad_exception異常是允許的,terminate()沒有被呼叫,並且這個bad_exception異常能被程式的其它部分捕獲。

  最終結果:最初從f()丟擲的long異常先被對映為int,再被對映為bad_exception。這樣的對映不但避免了前面導致terminate的再次異常問題,還給程式的其它部分一個修正的機會。bad_exception異常物件的存在表明了某處最初丟擲了一個unexpected異常。透過在問題點附近捕獲這樣的物件,程式可以得體地恢復。

  我也注意到一個奇怪的地方。在程式碼裡,你看到f()丟擲了一個long,my_unexpected_handler()丟擲了一個int,而沒人丟擲bad_exception,但main()確實捕獲到一個bad_exception。是的,程式捕獲了一個它從沒丟擲的物件。就我所知,唯一被允許發生這種行為的地方就是unexpected異常處理函式和bad_exception異常間的相互作用。

 

1.4  一個更特別的函式

  C++標準定義了3個“特別”函式來捕獲異常。其中,你已經看到了terminate()和unexpected()。最後,也是最簡單的一個是uncaght_exception()。摘自C++標準(15.5.3):

  函式bool uncaught_exception()在被丟擲的異常物件完成賦值到匹配的異常處理函式的異常申明完成初始化之間返回true。包括其中的退棧過程。如果異常被再次丟擲,uncaught_exception() 從再拋點到再拋物件被再次捕獲間返回true。

  uncaught_exception() 讓你檢視是否程式丟擲了一個異常而還沒有被捕獲。這個函式對解構函式有特別意義:

#include

#include

using namespace std;

 

class X

  {

public:

  ~X();

  };

 

X::~X()

  {

  if (uncaught_exception())

  printf("X::~X called during stack unwindn");

  else

  printf("X::~X called normallyn");

  }

 

int main()

  {

  X x1;

  try

  {

  X x2;

  throw 1;

  }

  catch (...)

  {

  }

  return 0;

  }

  在C++標準相容的環境下,程式輸出:

X::~X called during stack unwind

X::~X called normally

  x1和x2在main()丟擲異常前構造。退棧時呼叫x2的解構函式。因為一個未被捕獲的異常在解構函式呼叫期間處於活動狀態,uncaught_exception()返回true。然後,x1的解構函式被呼叫(在main()退出時),異常已經恢復,uncaught_exception()返回false。

  和以前一樣,Visual C++在這裡也不支援C++標準。在其下編譯,程式輸出:

X::~X called normally

X::~X called normally

  如果你瞭解Microsoft的SEH(我在第二部分講過的),就知道uncaught_exception()類似於SEH的AbnormalTermination()。在它們各自的應用範圍內,兩個函式都是檢測是否一個被丟擲的異常處於活動狀態而仍然沒有被捕獲。

 

1.5  小結

  大多數函式不直接拋異常,但將其它函式拋的異常傳遞出來。決定哪些異常被傳遞是非常困難的,尤其是來自於沒有異常規格申明的函式的。bad_exception ()是一個的閥門,提供了一個方法來保護那些你不能進行完全解析的異常。

  這些保護能工作,但,和普通的異常處理函式一樣,需要你明確地設計它。對每個可能違背其異常規格申明的函式,你都必須記得在其異常規格申明中加一個bad_exception並在某處捕獲它。bad_exception和其它異常沒有什麼不同:如果你不想捕獲它,不去產生它就行了。一個沒有並捕獲的bad_exception將導致程式終止,就象在最初的地方你沒有使用bad_exception進行替換一樣。

  異常規格申明使你意圖明確。它說“這是我允許這個函式丟擲的異常的集合;如果函式丟擲了其它東西,不是我的設計錯了就是程式有神經病(the program is gy)”。一個unexpected異常,不管它怎麼出現的,都表明了一個邏輯錯誤。我建議你最好讓錯誤以一種可預見的方式有限度地發生。

  所有這些表明你可以描繪你的程式碼在最開始時的異常的行為。不幸的是,這樣的描繪接近於巫術。下次,我將給出一些指導方針來分析你的程式碼中的異常。


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

相關文章