国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
c++中的異常處理
c++中的異常處理
2007-06-14 18:13

簡介

大型應用軟件往往是分層構建的。在最底層你會發(fā)現(xiàn)庫函數(shù),API函數(shù),和私有的底層函數(shù)。然而在最高層則是用戶接口組件,比如一個電子制表軟件讓用戶填寫數(shù)據(jù)表單。下面來看一種普通的航空訂票系統(tǒng):它的最高端是由一些GUI組件所組成,用來在用戶的屏幕上顯示內(nèi)容。這些高端組件與那些封裝了數(shù)據(jù)庫API的數(shù)據(jù)存取對象相互作用。再往底層一些,那些數(shù)據(jù)庫API與數(shù)據(jù)庫引擎相交互,然而數(shù)據(jù)庫引擎自己又會調用系統(tǒng)服務來處理底層的硬件資源,比如物理內(nèi)存,文件系統(tǒng)和安全模型。一般情況下,及其嚴格的運行期錯誤會在這些底層代碼中被檢測出來,但是它們不能-----或者說不應該----試圖自己處理這些錯誤。解決這些嚴格的運行期錯誤的責任應該由高端組件來承擔。為了解決一個錯誤,高端組件必須得到錯誤發(fā)生的通知。本質上,錯誤處理包括錯誤檢測和通知高端組件。這些組件依次處理錯誤并且試圖從錯誤中恢復。

傳統(tǒng)的錯誤處理方法

在早些時期,C++本身并沒有處理運行期錯誤的能力。取而代之的是那些傳統(tǒng)的C方法。這些方法可以被歸為三類設計策略:
返回一個狀態(tài)碼來表明成功或失敗
把錯誤碼賦值給一個全局標記并且讓其他的函數(shù)來檢測
終止整個程序

上述的任何一個方法在面向對象環(huán)境下都有明顯的缺點和限制。其中的一些根本就不可接受,尤其是在大型應用程序中。接下來的部分將會仔細檢查一下這些方法,目的是發(fā)現(xiàn)他們與生俱來的限制和危險性。

返回一個錯誤碼

在某種程度上這個方法是有用的,比如一個小型程序有著一致而且有限的錯誤碼存在,并且嚴格的報告錯誤和檢查一個函數(shù)返回值的策略被應用。然而,這種方法也有著顯著的局限性;例如,錯誤類型和它們的列舉值必須標準化。因為一個庫的實現(xiàn)者可能選擇返回值0來代表一個錯誤,然而另一個實現(xiàn)者卻選擇0來代表成功并且用那些非0值代表出現(xiàn)錯誤。通常,那些返回碼會在一個公共頭文件中以符號常量的形式存在,從而在整個軟件的開發(fā)過程中或者在一個開發(fā)團隊里達成一致。但是,這些碼并不是標準的。

不用說,在結合那些不兼容的軟件庫的時候,如何處理非標準的錯誤碼將會是一件極其頭疼的事。另外一個缺點是對于每一個返回碼都必須查閱和解釋------一個乏味并且昂貴的操作。這個策略的實現(xiàn)需要調用者在每一次調用的時候對返回值進行檢查,如果沒有這樣做將會導致運行期錯誤。當一個錯誤碼被檢測,就會終止正常的執(zhí)行流程并且把錯誤碼傳遞給調用者。那些附加的包裹每一個函數(shù)調用的代碼會很輕易的使程序的大小翻倍并且引起軟件維護和程序可讀性的降低。更糟的是,有時要想返回一個error value是不可能的。例如,構造函數(shù)沒有返回值,所以就不能應用這種方法在對象構造失敗的情況下報告錯誤。

求助于全局標記

一個可以選擇的用來報告運行期錯誤的途徑是使用全局標記,它表明了最后的操作是否成功。不像返回碼策略,這個方法是標準化的。C 的<errno.h>頭文件中定義了一種機制用來檢查和給一個全局整型標記errno賦值。這種策略固有的缺陷也是不能被忽視的。在一個多線程環(huán)境中,被一個線程賦予了一個錯誤碼的errno有可能不經(jīng)意的被另一個線程所改寫,而調用者還未對errno進行檢查。另外,對錯誤碼而不是一個更為可讀的信息的使用是很不利的,因為那些錯誤碼可能會在不同的環(huán)境中不兼容。最終,這種方法需要嚴格的良好的編程樣式,也就是不斷的對errno的當前值進行檢查。

全局標記策略和函數(shù)返回值策略是相似的:二者都提供一種機制來報告錯誤,但是二者卻都不能保證錯誤被處理。例如,一個函數(shù)沒有成功打開一個文件可以通過給errno賦予一個合適的值來表明錯誤的發(fā)生。然而,它不能阻止另一個函數(shù)試圖寫入和關閉那個文件。更進一步,如果errno表明一個錯誤并且程序員檢測到而且按照預期處理了它,那么errno還應該被顯式的復位。如果一個程序員忘記了做這件事,那么將會引起其他函數(shù)誤以為錯誤還沒有被處理,從而去校正那個問題,引起不可預知的結果。

終止程序

最為殘酷的處理運行期錯誤的方法是簡單的終止程序。這種解決方案去除了上面兩種方法的一些缺點;例如,沒有必要反復的檢查每個函數(shù)返回值的狀態(tài),而且程序員也不必賦值給一個全局標記,反復的測試和清除它的值。在標準C的函數(shù)庫中有兩個函數(shù)用來終止一個程序:exit()和abort()。exit()被調用能夠表明程序被成功終止,或者它可以在遇到運行期錯誤的時候被調用。在把控制權交還給運行環(huán)境之前,exit()首先會清空流和關閉打開的文件。abort()卻不一樣,它表示程序被意外終止,不會清空流和關閉打開的文件。

關鍵性的程序不應該在任何運行期錯誤存在的情況下突然終止。如果一個生命支持系統(tǒng)突然停止工作僅僅是因為它的控制器檢測到0做除數(shù),那么將是一種災難。同樣,一個控制由人駕駛的航天飛機自動運行的計算機系統(tǒng)也不應該因為暫時的和地面控制系統(tǒng)失去聯(lián)系就停止工作。類似的,電話公司的賬目系統(tǒng)或者銀行系統(tǒng)都不應該在運行期錯誤出現(xiàn)的時候就中止。健壯的真實世界的應用程序應該做的更好。
程序終止甚至對于應用程序都是有問題的。一個檢測到錯誤的函數(shù)通常都沒有必要的信息來衡量錯誤的嚴重性。例如一個內(nèi)存分配函數(shù)并不能說出內(nèi)存分配失敗是由于用戶正在使用調試器,網(wǎng)頁瀏覽器,電子制表軟件,文字處理軟件,還是由于系統(tǒng)因為硬件錯誤變得不穩(wěn)定。在第一種情況下,系統(tǒng)可以簡單的顯示一條信息來告訴用戶關閉不必要的應用程序。第二種情況下,就需要一種更為殘酷的措施了。然而,在終止程序的策略下,那個內(nèi)存分配函數(shù)就會簡單的終止程序,而不考慮錯誤的嚴重性。這種方法在一些關鍵性應用程序中是無法應用的。好的系統(tǒng)設計應該保證運行期錯誤被檢測和報告,但是它也應該確保最小限度的容錯水平。

終止程序在極限環(huán)境下或者在調試階段是可以被接受的。然而,abort()和exit()卻不應該在面向對象環(huán)境中使用,甚至即使在調試階段,因為他們并沒有意識到C++對象模型的存在。

exit()和abort()不銷毀對象

對象可以持有從構造函數(shù)或者某個成員函數(shù)中獲得的資源:從free store中分配的內(nèi)存,文件句柄,通信端口,I/O設備等等。這些資源必須在適當時候被釋放。通常,資源都是由析構函數(shù)來釋放。這種設計方法被稱為resource initialization is acquisition。在棧上建立的局部對象會自動銷毀。然而abort() 和exit()并不調用這些局部對象的析構函數(shù)。因此,程序的意外終止將會引起無法挽回的損害:數(shù)據(jù)庫被破壞,文件可能丟失,并且一些有價值的數(shù)據(jù)可能丟失。基于這個原因,請不要在面向對象環(huán)境中使用abort()和exit()。

進入異常處理

正如你所見,傳統(tǒng)C的錯誤處理方法并不適合C++,C++的一個設計目標就是讓用C++進行大規(guī)模軟件開發(fā)比C更好更安全。

C++的設計者們已經(jīng)意識到缺乏合適的錯誤處理機制使得實現(xiàn)這一目標相當?shù)睦щy。他們試圖尋找一種完全擺脫C的錯誤處理缺陷的解決方案。其中的一種想法就是建立在當異常被觸發(fā)的時候程序自動把控制權傳遞給系統(tǒng)。機制必須簡單,并且它能夠使程序員從不斷的檢查一個全局標記或者返回值的苦差事中解脫出來。另外,它還必須保證異常處理程序能夠自動獲得異常信息。最終它還要確保當一個異常沒有在本地處理的時候,本地對象能夠被適當?shù)匿N毀,并且把它所持有的資源釋放。

1989年,在多年的研究和多方建議下,異常處理進入C++。C++并不是第一個對結構化運行期錯誤處理進行支持的語言。早在20世紀60年代,PL/1就提供了一種內(nèi)建的異常處理機制;Ada也在20世紀80年代提供了自己的異常處理,另外還有幾種語言也做到了這一點。但是這些異常處理模型沒有一個適合C++對象模型和程序結構。因此,被提議的C++異常處理是獨一無二的,并且它已經(jīng)作為了一種模型出現(xiàn)在一些新產(chǎn)生的語言之中。

異常處理機制的實現(xiàn)被證明是一種挑戰(zhàn)。第一個C++編譯器,cfront,在UNIX環(huán)境下運行。和許多UNIX編譯器一樣,它首先是作為一個翻譯器把C++代碼轉換成C,接著再編譯C代碼。Cfront 4.0計劃引入異常處理,然而,異常處理機制的實現(xiàn)是如此的復雜,以至于cfront 4.0的開發(fā)團隊在用了一年時間設計它之后完全的放棄了這個項目。Cfront 4.0再也沒有出臺。然而,異常處理卻成為了標準C++的有機組成部分。后來出現(xiàn)的一些編譯器都支持了它。在接下來的部分里將會解釋為什么在cfront以及任何編譯器下實現(xiàn)異常處理是如此的困難。

實現(xiàn)異常處理所面臨的挑戰(zhàn)

實現(xiàn)異常處理所遇到的困難主要來自于以下幾個因素:

第一,實現(xiàn)必須保證對于某一異常的合適的handler被找到。

第二,異常對象必須是多態(tài)的;這樣,當實現(xiàn)無法通過派生類對象定位handler的時候可以考慮基類的handler。這種需要表明必須引入運行期類型檢測。然而那時C++還沒有任何運行期類型檢測的能力。因此這種能力必須首先被實現(xiàn)。

作為一個附加的復雜性,實現(xiàn)必須能夠調用所有局部對象的析構函數(shù)。這個過程被稱為stack unwinding 。因為早期的C++編譯器首先要把C++源文件轉換為純C,然后再把C代碼編譯成機器碼。異常處理的實現(xiàn)者們不得不用C來實現(xiàn)運行期類型鑒別和stack unwinding。幸運的是,這些障礙已經(jīng)被克服。

應用異常處理

異常處理是一種靈活并且精巧的工具。它克服了C的傳統(tǒng)錯誤處理方法的缺點并且能夠被用來解決一系列運行期錯誤。但是,異常處理也像其他語言特性一樣,很容易被誤用。為了能夠有效的使用這一特性,理解運行期機制是如何工作的以及相關的性能花費是非常重要的。接下來的部分里將會進入異常處理的內(nèi)部并且論證如何使用這一工具來建立安全的應用系統(tǒng)。

異常處理要素

異常處理是一種把控制權從異常發(fā)生的地點轉移到一個匹配的handler的機制。異常是內(nèi)建數(shù)據(jù)類型變量或者是對象。異常處理機制包括四個部分: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表達式以及函數(shù)所調用。throw表達式包括一個關鍵字throw以及assignment expression。例如:

try            {                 throw 5; // 5 is assigned to n in the following catch statement            }            catch(int n)            {            }

throw表達式和返回語句很相似。empty throw是沒有操作數(shù)的throw語句。例如:

throw;

在handler中的empty throw表明它在重新拋出異常,后面我們會討論到它。另外,如果目前沒有異常被處理,那么執(zhí)行一個empty throw將會調用terminate()。

Stack Unwinding

當一個異常被拋出,運行時機制首先在當前的作用域尋找合適的handler。如果不存在這樣一個handler,那么將會離開當前的作用域,進入更外圍的一層繼續(xù)尋找。這個過程不斷的進行下去直到合適的handler被找到為止。此時堆棧已經(jīng)被解開,并且所有的局部對象被銷毀。如果始終都沒有找到合適的handler,那么程序將會終止。注意,C++保證局部對象被適當?shù)匿N毀僅僅是在拋出的異常被處理的情況下。一個未被撲獲得異常是否引起局部對象的銷毀由實現(xiàn)決定的。為了保證局部對象的析構函數(shù)在異常未被捕獲情況下也能夠被正常調用,你應該在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的過程就好比一個返回語句序列,每一個都返回相同的對象給它的調用者。

傳遞異常對象給handler

一個異常能夠按值或者按引用的方式傳遞給它的handler。為異常對象分配的內(nèi)存是通過一種未被定義的途徑(但是并沒有在自由存儲區(qū))。一些實現(xiàn)使用專門的異常堆棧,在那里,異常對象被創(chuàng)建。當一個異常按引用的方式傳遞,handler獲得是在異常堆棧上建立的對象的引用。通過引用方式傳遞異常保證了它的多態(tài)行為。按值傳遞的異常被建立在調用者的堆棧上。例如:

#include <cstdio>            class ExBase {/*...*/};            class FileEx: public ExBase {/*...*/};            void Write(FILE *pf)            {                 if (pf == NULL) throw FileEx();                 //... process pf normally            }            int main ()            {                 try                 {                     Write(NULL); //will cause a FileEx exception to be thrown                 }                 catch(ExBase& exception) //catch ExBase or any object derived from it                 {                     //diagnostics and remedies }                 }            }

按值傳遞異常將會造成反復的復制對象,并且它的花費是昂貴的,因為異常對象在匹配的handler被找到以前會被構造和銷毀許多次。然而,在比較罕見的情況下也會發(fā)生按值傳遞,由于為了保持應用系統(tǒng)的整體性,性能考慮往往被放在了第二位。

異常類型匹配

異常的類型決定了哪個handler能夠捕獲它。異常的匹配規(guī)則比函數(shù)重載的匹配規(guī)則更為嚴格??紤]下面這種情況:

try            {                 throw int();            }            catch (unsigned int) //will not catch the exception from the previous try-block            {            }

拋出異常的類型是int型,然而handler卻期待一個unsigned int。異常處理機制不認為二者是能夠匹配的類型;結果,拋出的異常沒有被捕獲。異常匹配規(guī)則僅僅允許一個非常有限的轉換集。對于一個異常E和一個帶有T或T&參數(shù)的handler,符合下面的條件可以進行匹配:

T和E是同一類型(const 和volatile被忽略)
T是E的沒有歧義的公共基類

如果E和T都是指針類型,當二者的類型相同時可以進行匹配或者E所指向對象的類型公有無歧義的繼承自T指向對象的類型。

作為對象的異常

正如你所發(fā)現(xiàn)的,傳統(tǒng)的通過返回一個整型錯誤碼的方法在OOP中已經(jīng)不再適用。C++異常處理機制提供了更多的彈性,安全性和穩(wěn)固性。一個異常既可以是int 或char等基本類型,也可以是更為豐滿的對象,有著數(shù)據(jù)成員和成員函數(shù)。這樣一個對象可以為handler提供更多的選擇進行恢復。一個聰明的異常對象,可以通過成員函數(shù)返回錯誤的詳細描述,而不是讓handler查閱某個表或文件。它也可以擁有在錯誤被適當處理之后使程序從運行期錯誤中恢復的成員函數(shù)??紤]有這樣一個日志類想要添加新的紀錄到一個已存在的日志文件中:如果打開日志文件失敗,它會拋出一個異常。當它被匹配的handler所捕獲,異常對象能夠擁有一個成員函數(shù),這個成員函數(shù)建立一個對話框。操作者可以從對話框中選擇恢復方法,包括建立一個新的日志文件,選擇另一個日志文件,或者是允許系統(tǒng)在沒有日志的情形下運行。

Exception Specification

一個函數(shù)可以通過指定一個它所能拋出的異常列表來提醒它的用戶。Exception specifications在用戶只能看到函數(shù)的原型但是卻無法獲得它的源文件的時候將會十分的有用。下面是一個指定異常的例子:

class Zerodivide{/*..*/};            int divide (int, int) throw(Zerodivide); // function may throw an exception            // of type Zerodivide, but no other

如果你的函數(shù)永遠不會拋出任何異常,它可以像下面這樣聲明:

bool equals (int, int) throw(); //no exception is thrown from this function

注意一個函數(shù)被聲明為沒有exception specification 例如:

bool equals (int, int);

Exception specification在運行期生效

一個exception specification不會在編譯期被檢查,而是在運行期。當一個函數(shù)試圖拋出一個在exception specification中未被指定的異常的時候,異常處理機制將會檢測到這種違規(guī)并且調用標準函數(shù)unexpected()。unexpected()的默認行為是調用terminate()終止程序。違背exception specification就好比是一個bug,不應該發(fā)生,這就是為什么默認行為是終止程序。不過默認的行為也可以被改變,通過使用函數(shù)set_unexpected()。

因為exception specifications在運行期才有效,所以編譯期可能會故意忽略那些違背exception specifications的代碼。好比下面:

int f(); // no exception specification, f can throw any type of exception            void g(int j) throw() // g promises not to throw any exception at all            {                 int result = f(); // if f throws an exception, g will violate its guarantee                 //not to throw an exception. still, this code is legal            }

在上面這個例子中,函數(shù)g()并不允許拋出任何異常。它調用函數(shù)f(),然而f()卻可能拋出任何異常因為它沒有exception specification。如果f()拋出一個異常,它將會通過g()傳播出去,但是這卻破壞了g()不會拋出任何異常的保證。這也許看起來會很奇怪,有一些違背在編譯期就應該被發(fā)現(xiàn)報錯的,為什么一定要等到運行期呢?然而許多問題并不像想象的那么簡單,以下幾個原因就要求必須采用運行期檢測策略。在前面的那個程序中,f()可能是一個被遺留下來的C函數(shù)。我們不可能強迫每個C函數(shù)有exception specification。并且因為這個原因就強迫程序員在g()中寫不必要的try和catch(…)塊也是不實際的。通過強迫exception specification只在運行期才有效,C++采取了“信任程序員”的策略而不是強加負擔給程序員和實現(xiàn)。

Exception specification的一致性

C++需要派生類中的exception specification與基類保持一致。這意味著派生類的virtual function重載函數(shù)的exception specification必須是基類的限制性子集,例如:

// various exception classes            class BaseEx{};            class DerivedEx: public BaseEx{};            class OtherEx {};            class A            {            public:            virtual void f() throw (BaseEx);            virtual void g() throw (BaseEx);            virtual void h() throw (DerivedEx);            virtual void i() throw (DerivedEx);            virtual void j() throw(BaseEx);            };            class D: public A            {            public:            void f() throw (DerivedEx); //OK, DerivedEx is derived from BaseEx            void g() throw (OtherEx); //error; exception specification is            //incompatible with A's            void h() throw (DerivedEx); //OK, identical to the exception            //specification in base            void i() throw (BaseEx); //error, BaseEx is not a DerivedEx nor is it            //derived from DerivedEx            void j() throw (BaseEx,OtherEx); //error, less restrictive than the            //specification of A::j            };

相同的一致性限制也應用于函數(shù)指針。一個擁有exception specification函數(shù)指針只能被賦予一個有著相同或更為局限的exception specification的函數(shù)。這說明一個沒有exception specification的函數(shù)指針不能被賦予一個有exception specification的函數(shù)。注意,因為exception specification不能被認為是函數(shù)類型的一部分,因此你不能聲明兩個僅僅是exception specification不同的函數(shù)。例如:

void f(int) throw (Y);            void f(int) throw (Z); //error; redefinition of 'void f(int)'

同樣的原因,聲明一個包含exception specification的typedef也是錯誤的:

typedef void (*PF) (int) throw(Exception); // error

在對象構造和銷毀時出現(xiàn)異常

構造函數(shù)和析構函數(shù)被自動調用,并且它們不能夠利用返回值來表明發(fā)生運行期錯誤。從表面上看,在對象構造和銷毀時拋出一個異常似乎是報告運行期錯誤的最好方法。但事實上你還必須考慮一些額外的因素。你尤其應該對從析構函數(shù)中拋出異常保持警惕。

從析構函數(shù)中拋出異常是危險的

從析構函數(shù)中拋出異常是不應該被推薦的,這是因為一個析構函數(shù)可能會在另一個異常進行stack unwinding的時候被調用,在這種情況下,異常處理機制就會調用terminate()終止程序。如果你真的想從一個析構函數(shù)中拋出異常的話,一種可取的做法是首先檢查一下是否還有未被捕獲的異常存在。

檢查未被捕獲的異常

一個異常被捕獲是在它相應的handler被找到的情況下。為了檢查一個異常是否被捕獲,你可以使用標準函數(shù)uncaught_exception()(它被定義在標準頭文件)。例如:

class FileException{};            File::~File() throw (FileException)            {            if ( close(file_handle) != success) // failed to close current file?            {            if (uncaught_exception() == true ) // is there any uncaught exception            //being processed currently?            return; // if so, do not throw an exception            throw FileException(); // otherwise, it is safe to throw an exception            // to signal an error            }            return; // success            }

然而,一個更好的選擇是直接在析構函數(shù)內(nèi)部處理異常,而不是讓他們擴散到外面。例如:

void cleanup() throw (int);            class C            {            public:            ~C();            };            C::~C()            {            try            {            cleanup();            }            catch(int)            {            //handle the exception within the destructor            }            }

如果一個異常被函數(shù)cleanup()拋出,那么它在析構函數(shù)內(nèi)部就被處理。否則,被拋出的異常就會傳播到析構函數(shù)的外部,并且如果這個析構函數(shù)是在stack unwinding 的過程中被調用,那么程序將會通過terminate()的調用而終止。

全局對象:構造和銷毀

我們都知道,全局對象的構造發(fā)生在程序開始之前。因此,任何從全局對象的構造函數(shù)中拋出的異常將不會被捕獲。這一點對于全局對象的析構函數(shù)也是一樣的-----全局對象的析構函數(shù)在程序結束之后被運行。因此,一個從全局對象的析構函數(shù)中拋出的異常也不會被捕獲。

高級異常處理技術

簡單的try-throw-catch模型可以被擴展來處理更為復雜的運行期錯誤。這一節(jié)將會討論一些更為高級的異常處理技術,包括異常層次,重新拋出異常,function try blocks以及auto_ptr 類。

標準異常

C++定義了一個標準異常層次,當在運行時發(fā)生反常情形時拋出。標準異常類從std::exception(在頭文件中定義)派生。這一層次使得應用程序能夠在單一的catch語句中捕獲這些異常:

catch (std::exception& exc)            {            // handle exception of type std::exception as well as            //any exception derived from it            }

那些通過語言內(nèi)建操作符拋出的標準異常是:

std::bad_alloc //by operator new            std::bad_cast //by operator dynamic_cast < >            std::bad_typeid //by operator typeid            std::bad_exception //thrown when an exception specification of 

所有的標準異常都提供了成員函數(shù)what(),它返回一個用來描述異常細節(jié)的字符串。注意,標準庫還有另外一個被它的組件拋出的的異常集合。

異常處理層次

異常在一個自下向上的層次中捕獲:派生層次越深的異常越先被處理,例如:

#include <stdexcept>            #include <iostream>            using namespace std;            int main()            {            try            {            char * buff = new char[100000000];            //...use buff            }            catch(bad_alloc& alloc_failure) // bad_alloc is            //derived from exception            {            cout<<"memory allocation failure";            //... handle exception thrown by operator new            }            catch(exception& std_ex)            {            cout<< std_ex.what() <<endl;            }            catch(...) // exceptions that are not handled elsewhere are caught here            {            cout<<"unrecognized exception"<<endl;            }            return 0;            }

派生層次越深的handler必須出現(xiàn)在其基類的前面。這是因為handler的匹配過程是按照出現(xiàn)的順序進行的。因此有可能某個handler永遠不會被執(zhí)行,例如,把一個處理派生類異常的handler放在處理基類異常的handler的后面。例如:

catch(std::exception& std_ex) //bad_alloc exception is always handled here            {            //...handle the exception            }            catch(std::bad_alloc& alloc_failure) //unreachable            {            cout<<"memory allocation failure";            }

重新拋出異常

異常的拋出表明了一種反常的狀態(tài)。先捕獲到異常的handler試圖解決這個問題,但是它如果沒有成功或者只完成了部分恢復,那么它可以重新拋出這個異常,讓更高一層的try block來處理它?;谶@種目的,try blocks可以在一個分等級的順序上進行嵌套,使得一個從低層重新拋出的異常能夠被重新捕獲。重新拋出用一個沒有操作數(shù)的throw語句來表示。例如:

#include <iostream>            #include <string>            using namespace std;            enum {SUCCESS, FAILURE};            class File            {            public: File (const char *) {}            public: bool IsValid() const {return false; }            public: int OpenNew() const {return FAILURE; }            };            class Exception {/*..*/}; //general base class for exceptions            class FileException: public Exception            {            public: FileException(const char *p) : s(p) {}            public: const char * Error() const { return s.c_str(); }            private: string s;            };            void func(File& );            int main()            {            try //outer try            {            File f ("db.dat");            func; // 1            }            catch(...) // 7            //this handler will catch the re-thrown exception;            //note: the same exception type is required            {            cout<<"re-thrown exception caught";            }            return 0;            }            void func(File & f)            {            try //inner try            {            if (f.IsValid() == false )            throw FileException("db.dat"); // 2            }            catch(FileException &fe) // 3            //first chance to cope with the exception            {            cout<<"invalid file specification" <<fe.Error()<<endl;            if (f.OpenNew() != SUCCESS) (5)            //re-throw the original exception and let a higher handler deal with it            throw; // 6            }            }

在上面的例子中,函數(shù)func()在main()中的try block里被調用(1)。第二個在func()中的try block拋出一個FileException類型的異常(2)。這個異常被func()內(nèi)的catch block所捕獲(3)。那個catch block試圖通過打開一個新文件進行補救,但是失敗了(5),并且FileException異常被重新拋出(6)。最終,那個重新拋出的異常被main()中的catch(…)所捕獲(7)。

Function try Blocks

Function try blocks是一個函數(shù)體本身就含有一個try block以及它的相關handler的函數(shù)。比如:

class Bad{};            void foo()try            {            throw Bad();            }            catch(...)            {            std::cout<<"error catch!!";            }

function try block使得一個handler能夠捕獲構造函數(shù)中以及初始化列表中發(fā)生的異常。然而,它并不像普通異常的handler,function try block很少能夠捕獲異常繼續(xù)對象的構建。這是因為被部分構造的對象要被銷毀。另外,一個function try block的handler不能執(zhí)行返回語句(或者說,handler必須通過一個throw離開)。那么究竟function try block的用處是什么呢?handler使得你可以拋出另一個異常而不是你剛才捕獲的那個,這樣可以阻止一個違背exception specification的情況發(fā)生。例如:

class X{};            C::C(const std::string& s) throw (X) // allowed to throw X only            try            : str(s) // str's constructor might throw a bad_alloc exception,            // might violate C's exception specification            {            // constructor function body            }            catch (...) //handle any exception thrown from ctor initializer or ctor body            {            //...            throw X(); //replace bad_alloc exception with an exception of type X            }

在這個例子中,一個string對象首先被創(chuàng)建作為class c 的一個成員。String在它的創(chuàng)建過程中可能拋出一個bad_alloc異常。那個function try block能夠捕獲bad_alloc異常并且拋出類型為x的異常使得它滿足c的構造函數(shù)的exception specification的需要。

異常處理的性能分析

異常處理機制主要取決于運行期類型檢查,當一個異常被拋出,實現(xiàn)必須確定異常是不是從try block中拋出。如果異常是從try block中拋出的話,實現(xiàn)就需要比較異常的類型,試圖從當前的作用域中找到匹配的handler。如果找到控制權將會傳遞給handler。然而這些都是樂觀的情況。如果實現(xiàn)沒有找到一個匹配的handler的話,或者異常不是從try block中拋出,那么就會進行stack unwinding,這個過程會一直進行下去直到一個匹配的handler被找到為止。當匹配的handler未被找到,terminate()就會被調用,終止程序。

額外的運行期類型信息

異常處理機制為了完成異常和它的handler之間的運行期匹配,它必須儲存關于每個異常對象的類型以及每個catch語句的額外信息。因為異??梢允侨魏晤愋停⑶乙部梢允嵌鄳B(tài)的,所以它的動態(tài)類型信息必須被獲得通過使用runtime type information(RTTI),RTTI給程序在速度和大小上增加了額外的負擔。并且只有RTTI還不夠,實現(xiàn)也需要運行期代碼信息,關于每個函數(shù)的結構。這些信息需要拿來確定一個異常是不是從try block中拋出。這個信息可以通過編譯器以下面的方式產(chǎn)生:編譯器把每個函數(shù)體劃分為三個部分:第一個部分在try block之外并且沒有活動的對象,第二個部分也在try block之外但是有活動的對象而且要通過stack unwinding來進行銷毀,第三部分在try block中。

抓住對異常處理的支持

異常處理技術的實現(xiàn)在不同的編譯器以及平臺下是不一樣的。但是它們都會強加額外的負擔給程序,即使沒有異常被拋出。一些編譯器可以選擇是否對異常處理進行支持。當異常處理被關掉的時候,那些額外的數(shù)據(jù)結構,查找表,以及一些附加的代碼都不會被生成。然而,關閉異常處理往往很少被選擇,因為即使你不直接使用異常,你也會隱含的使用它們:例如operator new,會拋出std::bad_alloc異常;STL容器可能會拋出他們自己的異常;第三方代碼庫也可能使用異常。因此,只有在你導入純C代碼的時候,才應該考慮關閉異常處理來避免額外的負擔。

異常的誤用

異常處理并不是用來限制出現(xiàn)錯誤,一些程序員可能會簡單的使用它來作為循環(huán)的選擇控制結構。例如,一個簡單的應用程序讓用戶輸入數(shù)據(jù)直到一個特定的條件滿足:

#include <iostream>            using namespace std;            class Exit{}; //used as exception object            int main()            {            int num;            cout << "enter a number; 99 to exit" << endl;            try            {            while (true) //infinitely            {            cin >> num;            if (num == 99)            throw Exit(); //exit the loop            cout<< "you entered: " << num << "enter another number " <<endl;            }            }            catch (Exit& )            {            cout<< "game over" <<endl;            }            return 0;            }

在上面的例子中,程序員把一個無限循環(huán)放在了try block中,throw語句終止循環(huán)并且把控制權傳遞給后面的catch語句。這種編程樣式不應該被推薦。它的效率會非常低下因為異常處理存在。在上面小的演示程序中,或許僅僅是程序樣式的差異,但是在大規(guī)模的應用系統(tǒng)中,使用異常處理來作為控制選擇控制結構的話,那么將會帶來顯著的效率損失。

結論

C++的異常處理機制克服了傳統(tǒng)方法所帶來的問題。它使得程序員從檢查函數(shù)的返回狀態(tài)的乏味的代碼中解放出來。另外一個重要的優(yōu)點是自動的stack unwinding,它保證了局部活動對象被正確銷毀以及他們的資源被安全釋放。

實現(xiàn)異常處理機制并不是一項簡單的工作。對獲取異常的動態(tài)類型的需要使得RTTI被引入C++。異??梢园凑辗N類進行分組,標準異常就是一個很好的例子。在最近幾年里,一些異常處理機制已經(jīng)得到了修正。第一個就是把exception specifications加入到了函數(shù)原型中。另一個是function try block的引入,它使得程序能夠處理從構造函數(shù)體或初始化列表中拋出的異常。

異常處理是一個用來有效的處理運行期錯誤的非常強大并且靈活的工具。然而使用它也是有代價的。


本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
C 與C++中的異常處理
try-catch和throw,throws的區(qū)別和聯(lián)系
c 中try catch的用法
PHP的Try, throw 和 catch
C++ 異常處理
關于java異常處理的幾個關鍵字 try catch/throw/throws
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服