進入異常處理 實現異常處理所面臨的挑戰 應用異常處理 異常處理要素 throw; 在handler中的empty throw表明它在重新拋出異常,后面我們會討論到它。另外,如果目前沒有異常被處理,那么執行一個empty throw將會調用terminate()。 Stack Unwinding
正如你所見,傳統C的錯誤處理方法并不適合C++,C++的一個設計目標就是讓用C++進行大規模軟件開發比C更好更安全。
C+
+的設計者們已經意識到缺乏合適的錯誤處理機制使得實現這一目標相當的困難。他們試圖尋找一種完全擺脫C的錯誤處理缺陷的解決方案。其中的一種想法就是建
立在當異常被觸發的時候程序自動把控制權傳遞給系統。機制必須簡單,并且它能夠使程序員從不斷的檢查一個全局標記或者返回值的苦差事中解脫出來。另外,它
還必須保證異常處理程序能夠自動獲得異常信息。最終它還要確保當一個異常沒有在本地處理的時候,本地對象能夠被適當的銷毀,并且把它所持有的資源釋放。
1989
年,在多年的研究和多方建議下,異常處理進入C++。C++并不是第一個對結構化運行期錯誤處理進行支持的語言。早在20世紀60年代,PL/1就提供了
一種內建的異常處理機制;Ada也在20世紀80年代提供了自己的異常處理,另外還有幾種語言也做到了這一點。但是這些異常處理模型沒有一個適合C++對
象模型和程序結構。因此,被提議的C++異常處理是獨一無二的,并且它已經作為了一種模型出現在一些新產生的語言之中。
異常處理機制的實現被證明
是一種挑戰。第一個C++編譯器,cfront,在UNIX環境下運行。和許多UNIX編譯器一樣,它首先是作為一個翻譯器把C++代碼轉換成C,接著再
編譯C代碼。Cfront 4.0計劃引入異常處理,然而,異常處理機制的實現是如此的復雜,以至于cfront
4.0的開發團隊在用了一年時間設計它之后完全的放棄了這個項目。Cfront
4.0再也沒有出臺。然而,異常處理卻成為了標準C++的有機組成部分。后來出現的一些編譯器都支持了它。在接下來的部分里將會解釋為什么在cfront
以及任何編譯器下實現異常處理是如此的困難。
實現異常處理所遇到的困難主要來自于以下幾個因素:
第一,實現必須保證對于某一異常的合適的handler被找到。
第二,異常對象必須是多態的;這樣,當實現無法通過派生類對象定位handler的時候可以考慮基類的handler。這種需要表明必須引入運行期類型檢測。然而那時C++還沒有任何運行期類型檢測的能力。因此這種能力必須首先被實現。
作
為一個附加的復雜性,實現必須能夠調用所有局部對象的析構函數。這個過程被稱為stack unwinding
。因為早期的C++編譯器首先要把C++源文件轉換為純C,然后再把C代碼編譯成機器碼。異常處理的實現者們不得不用C來實現運行期類型鑒別和stack
unwinding。幸運的是,這些障礙已經被克服。
異常處理是一種靈活并且精巧的工具。它克服了C的傳統錯誤處理方法的缺點并且能夠被用來解決一系列運行期錯誤。但是,異常處理
也像其他語言特性一樣,很容易被誤用。為了能夠有效的使用這一特性,理解運行期機制是如何工作的以及相關的性能花費是非常重要的。接下來的部分里將會進入
異常處理的內部并且論證如何使用這一工具來建立安全的應用系統。
異常處理是一種把控制權從異常發生的地點轉移到一個匹配的handler的機制。異常是內建數據類型變量或者是對象。異常處理
機制包括四個部分:a try block,一個或多個和try block相關的handler,throw表達式,以及異常自己。Try
block包含可能拋出異常的代碼。例如:
try
{
int * p = new int[1000000]; //may throw std::bad_alloc
}
一個try block后面將跟有一個或多個catch語句或者說是handlers, 每一個handler 處理不同類型的異常。例如:
try
{
int * p = new int[1000000]; //may throw std::bad_alloc
//...
}
catch(std::bad_alloc& )
{
}
catch (std::bad_cast&)
{
}
handler僅僅被在try block中的throw表達式以及函數所調用。throw表達式包括一個關鍵字throw以及assignment expression。例如:
try
{
throw 5; // 5 is assigned to n in the following catch statement
}
catch(int n)
{
}
throw表達式和返回語句很相似。empty throw是沒有操作數的throw語句。例如:
當一個異常被拋出,運行時機制首先在當前的作用域尋找合適的handler。如果不存在這樣一個
handler,那么將會離開當前的作用域,進入更外圍的一層繼續尋找。這個過程不斷的進行下去直到合適的handler被找到為止。此時堆棧已經被解
開,并且所有的局部對象被銷毀。如果始終都沒有找到合適的handler,那么程序將會終止。注意,C++保證局部對象被適當的銷毀僅僅是在拋出的異常被
處理的情況下。一個未被撲獲得異常是否引起局部對象的銷毀由實現決定的。為了保證局部對象的析構函數在異常未被捕獲情況下也能夠被正常調用,你應該在
main()里加入捕獲任何異常的catch語句。例如:
int main()
{
try
{
//...
}
catch(std::exception& stdexc) // handle expected exceptions
{
//...
}
catch(...) // ensure proper cleanup in the case of an uncaught exception
{
}
return 0;
}
stack unwinding的過程就好比一個返回語句序列,每一個都返回相同的對象給它的調用者。
原文轉自:http://www.anti-gravitydesign.com