一、先討論異常被引發後,可能導致的問題
意外異常:
如果它是在帶異常規範的函式中引發的,則必須與規範列表中的某種異常匹配,否則為意外異常。在預設情況下,這將導致程式異常終止(雖然c++11摒棄了異常規範,但仍支援它,且有些現有的**使用了它)。
未捕獲異常:
如果異常不是在函式中引發的,則必須捕獲它。如果沒**獲(在沒有try塊或沒有匹配的catch塊時,將出現這種情況),則異常被稱為未捕獲異常。
在預設情況下,這兩種異常將導致程式異常終止。當然可以修改程式對意外異常和未捕獲異常的反應。
未捕獲異常不會導致程式立刻異常終止。相反,程式將首先呼叫函式terminate()。在預設的情況下,terminate()呼叫abort()函式。
可以指定terminate()應呼叫的函式來修改terminate()的這種行為。
為此,可以呼叫set_terminate()函式。
set_terminate()和terminate()都是在標頭檔案exception中宣告的。
typedef void (* terminate_handler) ();
terminate_handler set_terminate(terminate_handler f) throw(); //c++98
terminate_handler set_terminate(terminate_handler f) noexcept; //c++11
void terminate(); //c++98
void terminate() noexcept; //c++11
假設希望未捕獲的異常導致程式列印一條訊息,然後呼叫exit()函式,將退出狀態值設定為5。
首先,請包含標頭檔案exception。可以使用using編譯指令、適當的using宣告或std::限定符,來使其宣告可用。
#include
using namespace std;
然後,設計乙個完成上述兩種操作所需的函式,其原型如下:
void myquit()
最後,在程式的開頭,將終止操作指定為呼叫該函式
set_terminate(myquit);
現在,如果引發了乙個異常且沒有**獲,程式將呼叫terminate(),而後者將呼叫myquit()。
接下來看一下意外異常;
通過給函式指定異常規範,可以讓函式的使用者知道要捕獲哪些異常。
假設函式的原型如下:
double argh() throw(out_if_bounds);
則可以這樣使用該函式:
try
catch(out_of_bounds & ex)
知道應捕獲哪些異常很有幫助,因為預設情況下,未捕獲的異常將導致程式異常終止。
原則上,異常規範應包含函式呼叫的其他函式引發的異常。例如,如果argh()呼叫了duh()函式,而後者可能引發retort物件異常,則argh()和duh()的異常規範中斗應包含retort。
除非自己編寫所有的函式,並且特別仔細。否則無法保證上述工作都已正確完成。
所以這也表明異常規範機制處理起來比較麻煩,這也是c++11將其摒棄的原因之一。
在這種情況之下,行為與未捕獲異常極其相似。如果發生意外異常,程式將呼叫unexpected()函式,
後者在預設情況下將呼叫abort()。
正如有乙個可用於修改terminate()的行為的set_terminate()函式一樣。
也有乙個可用於修改unexpected()行為的set_unexpected()函式。這些新函式也是在標頭檔案中exception中宣告的:
typedef void(* unexpected_handler) ();
unexpected_handler set_unexpected(unexpected_handler f) throw(); //c++98
unexpected_handler set_unexpected(unexpected_handler f) noexcept; //c++11
void unexpected(); //c++98
void unexpected() noexcepted; //c+0x
然而,與提供給set_terminate()函式的行為相比,提供給set_unexpected()的函式的行為受到更嚴格的限制。具體地說,unexpected_handler函式可以:
1、通過呼叫terminate()(預設行為)、abort()或exit()來終止程式;
2、引發異常。
引發異常的結果取決於unexpected_handler函式所引發的異常以及引發意外異常的函式的異常規範;
1、如果新引發的異常與原來的異常規範匹配,則程式將從那裡開始進行正常處理,即尋找與新引發異常匹配的catch塊。基本上,這種方法將用預期的異常取代意外異常。
2、如果新引發的異常與原來的異常規範不匹配,且異常規範中沒有包括std::bad_exception型別,則程式將呼叫terminate()、bad_exception是從exception派生而來的,其宣告位於標頭檔案exception中。
3、如果新引發的異常與原來的異常規範不匹配,且異常規範中包括std::bad_exception型別,則不匹配的異常將被std::bad_exception異常所取代。
總之,要捕獲所有的異常(不管是預期的異常還是意外異常),則可以這樣做:
首先確保異常標頭檔案的宣告可用:
#include
using namespace std;
然後,設計乙個替代函式,將意外異常轉換為bad_exception異常,該函式的原型如下:
void myunexpected()
僅使用throw,而不指定異常將導致重新引發原來的異常。然而,如果異常規範中包含了這種型別,則該異常將被bad_exception物件所取代。
接下來在程式的開始位置,將意外異常操作指定為呼叫該函式:
set_unexpected(myunexpected);
最後,將bad_exception型別包括在異常規範中,並新增如下catch塊序列:
double argh() throw(out_of_bounds, bad_exception);
...try
catch(out_of_bounds & ex)
catch(bad_exception & ex)
二、有關異常的注意事項
從前面關於如何使用異常的討論可知,應在設計程式時就加入異常處理功能,而不是以後再新增。
這樣做有些缺點。
例如,使用異常會增加程式**,降低程式的執行速度。
異常規範不適用於模板,因為模板函式引發的異常可能隨特定的具體化而異。
異常和動態記憶體分配並非總能協同工作。
下面進一步討論動態記憶體分配和異常。
void test1(int n)
string類採用動態記憶體分配。通常,當函式結束時,將為mesg呼叫string的析構函式。
雖然throw語句過早地終止了函式。但它仍然使得析構函式被呼叫,這要歸功於棧解退。
因此在這裡,記憶體被正確地管理。
接下來看這個函式:
void test2(int n)
這裡有個問題,解退棧的時候,將刪除棧中的變數ar。但函式過早地終止意味著函式末尾的delete語句被忽略。
指標消失了,但它指向的記憶體塊未被釋放,並且不可訪問。總之,這些記憶體被洩漏了。
當然這種洩漏是可以被避免的。例如,可以再引發異常的函式中捕獲該異常,在catch塊中包含一些清理**,然後重新引發異常:
void test3(int n)
catch (exception & ex)
...delete ar;
return;
}但是這樣做,仍然會增加疏忽和產生其他錯誤的機會。另一種解決方法是使用智慧型指標模板。
總之,雖然異常處理對於某些專案極為重要,但它也會增加程式設計的工作量,增大程式,降低程式的速度。
另一方面,不進行錯誤檢查的代價可能非常高。
三、異常處理
現代庫中,異常處理的複雜程度可能再創新高。
理解庫中的異常處理像學習語言本身一樣困難。
現代庫中包含的例程和模式可能像c++語法細節一樣陌生而困難。
要開發出優秀的軟體,必須花時間了解庫和類中的複雜內容,就像必須花時間學習c++本身一樣。
通過庫文件和源**了解到的異常和錯誤處理細節將使程式設計師和他的軟體受益。
事務異常注意事項
主要點 try.catch不會返回物件錯誤或者字段錯誤等型別的錯誤當 set xact abort 為 on 時,如果執行 transact sql 語句產生執行時錯誤,則整個事務將終止並回滾。當 set xact abort 為 off 時,有時只回滾產生錯誤的 transact sql 語句,而...
java異常的注意事項
我們在捕獲到異常後,在cath中列印了異常資訊,並且向上丟擲了異常,這時候異常資訊不能列印堆疊資訊,只有乙個錯誤提示,如果不呼叫initcause是無法列印出所有異常鏈的如下所示 catch exception e 以上 無法列印堆疊資訊,應該改為 catch exception e 或者在自定義異...
Java異常的注意事項
子類在覆蓋父類方法時,父類方法如果丟擲了異常 那麼子類的方法只能丟擲父類的異常或者該異常的子類 如果父類丟擲多個異常,那麼子類只能丟擲父類異常的子集 簡單來說,子類覆蓋父類的方法,只能丟擲父類的異常的子集。注意 如果父類方法沒有丟擲異常,那麼子類覆蓋時絕對不可能拋,只能try。class aexte...