c++異常處理的實現

我們編寫的程序一般要滿足正確性、健壯性、易讀性和可複用性、可擴展性。健壯性指程序既能處理正確流程的情況,也能處理非法的錯誤的異常情況,提示用戶出現了什麼問題,一般由異常處理實現。在編寫小型的程序時,可以很容易發現程序的錯誤,異常提示顯得不那麼重要,但在大型的由多人共同完成的程序中往往難以發現出現了什麼問題,因此異常提示和處理顯得尤爲重要。下面我們瞭解c++的異常處理的實現。

異常機制提供程序中錯誤檢測和錯誤處理部分之間的通信。C++的異常處理中包括:

throw表達式:錯誤檢測部分拋出一個異常,說明程序遇到了無法處理的問題

try塊:錯誤處理部分使用它處理異常。try語句塊以try關鍵字開始,並以一個或多個catch子句結束。在try塊中執行的代碼所拋出的異常,通常會被其中一個catch子句處理。

由標準庫定義的一組異常類,用來在throwcatch之間傳遞有關的錯誤信息。exception頭文件定義了最常見的異常類,它的類名是exceptionstdexcept頭文件定義了幾種常見的異常類,new頭文件定義了bad_alloc異常類,提供因無法分配內存而由new拋出的異常,type_info頭文件定義了bad_cast異常類。exceptionbad_allo bad_cast只定義了默認構造函數,其他的異常類型則只定義了一個使用string初始化的構造函數,用於提示錯誤信息,異常類型只定義了一個what操作,這個函數沒有參數,返回const char*類型,爲該一場提供文字信息。異常類的繼承層次如下圖所示:

<!--[endif]-->

我們來看一個例子

int triangle(int a, int b, int c)

{

if(a<0 || b<0 || c<0 || a+b<=c || a+c<=b || b+c<=a)

throw runtime_error("The lengths of three sides can't form triangle");

return a + b + c;

}

int main()

{

int total = 0;

try

{

total = triangle(3,4,7);

}

catch(const runtime_error& e)

{

cout<<e.what()<<endl;

}

cout<<total<<endl;

return 0;

}

函數int triangle(int a, int b, int c)求三角形三邊之和,函數首先判斷這三條邊能否構成一個三角形,不能就拋出一個異常轉入異常處理塊中。

異常是通過拋出(throw)對象而引發的,該對象的類型決定應該激活哪個catch子句,被選中的代碼是調用鏈中與該對象類型匹配且離拋出異常位置最近的的那個。異常以類似於將實參傳遞給函數的方式拋出和捕獲。

執行throw的時候,不會執行跟在throw後面的語句,而是將控制從throw轉到匹配的catch,該catch可以是同一函數中局部的catch,也可以在直接或間接調用發生異常的函數的另一個函數中。控制從一個地方轉移到另一個地方,有兩個重要含義:

<!--[if !supportLists]-->(1) <!--[endif]-->沿着調用鏈的函數提早退出

<!--[if !supportLists]-->(2) <!--[endif]-->一般而言,在處理異常的時候,拋出異常的塊中的局部存儲不存在了。

拋出異常時對象爲指針解引用形式時,異常對象的類型與指針的靜態類型相同,不論指針是否指向其派生類型。通常拋出指針是個壞主意,發生異常時拋出異常塊的局部存儲被釋放了。

拋出異常的的時候,暫停當前函數的執行,開始查找匹配的catch子句,首先檢查throw本身是否在try塊內,如果是,檢查與該try相關的catch子句,看是否其中之一與被拋出的對象匹配。如果找到匹配的catch,就處理異常,如果找不到,就退出當前函數,並且繼續在調用函數中查找。這個過程稱爲“棧展開”

棧展開期間,提早退出包含throw的函數和相關的調用它的函數,每個函數中的局部對象都會撤銷,如果是類類型的,就調用其析構函數。考慮這個問題,如果在調用析構函數時跑出了新的異常,編譯器怎麼辦?是取代舊的異常還是忽略新的異常?當然,析構函數應保證不會拋出異常。

構造函數有可能會拋出異常,甚至發生在初始化表中。爲了處理來自構造函數初始化表的異常,必須將構造函數初始化爲函數測試塊。可以使用函數測試塊將一組catch子句與函數連成一個整體。

template<class T> Handle<T>::T(T* p)

try:ptr(p),use(new size_t(1))

{

}

catch(const bad_alloc& e)

{

handle_out_of_memory(e);

}

如果最終沒有找到匹配的catch子句,則調用庫函數terminate.

catch子句的匹配:比實參和形參的匹配規則更嚴格,大部分轉換都不允許,出下面幾個可能的區別之外,允許從非constconst的轉換,允許從派生類型到基類類型的轉換,將數組轉化爲指向數組類型的指針,將函數轉化爲指向函數類型的適當指針。

異常說明符:異常對象本身是拋出對象的副本,是否再次複製取決於異常說名符(其實就是catch形參)。基類的異常說明符可以捕獲派生類的異常對象,而且,異常說明符的靜態類型決定catch子句可以執行的動作。如果被拋出的對象類型是派生類型的,但由接受基類型的catch子句處理,那麼catch不能使用派生類的特有的成員函數。

catch子句的順序必須反映類型層次。

有可能單個catch不能完全處理一個異常,在進行了一些矯正行動後,catch可能確定該異常必須有函數調用鏈中更上層的函數處理,catch可以通過重新拋出將異常傳遞給函數調用鏈中更上層的函數。重新拋出是後面不跟類型或表達式的一個throw

throw

throw語句將重新拋出異常,他只能出現在catch或從catch調用的函數中。被拋出的異常是原來的異常對象,而不是catch形參。

捕獲所有異常的catch子句爲:

catch(…)

{

}

異常說明:跟在函數形參表之後,在關鍵字throw後跟着一個(或爲空)由圓括號括住的異常類型列表。

void recoup(int) throw(runtime_error);

空說明列表指出函數不拋出任何異常。

void no_problem() throw();

如果一個函數沒有指出異常說明,則該函數可以拋出任意類型的異常。

異常說明有用的一種情況是,如果函數可以保證不拋出任何異常。

派生類虛函數的異常說明符必須比對應基類虛函數的異常說明符同樣嚴格,或者更受限。

可以在函數指針的定義中提供異常說明符,在用另一指針初始化帶異常說明的函數指針,或者將後者賦值給函數地址的時候,兩個指針的異常說明符不必相同,但是,源指針的異常說明符必須至少與目標指針的一樣嚴格,甚至更嚴格。

void (*pf)(int) throw() = recoup; //error

發佈了18 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章