版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
1、這兩天要處理一個異常的問題,剛好查了些相關(guān)的資料。在網(wǎng)上看到了一個不錯的貼 子,就轉(zhuǎn)了過來,方便本人,以及來此旅游的朋友學習。源地址: nt.html?91983,1異常處理的基本思想是簡化程序的錯誤代碼,為程序鍵壯性提供一個標準檢測機制。也 許我們已經(jīng)使用過異常,但是你會是一種習慣嗎,不要老是想著當我打開一個文 件的時候才用異常判斷一下,我知道對你來說你喜歡用return value 或者是printerror message 來做,你想過這樣做會導致Memory Leak,系統(tǒng)退出,代碼重復 /難讀,垃圾一堆 .嗎?現(xiàn)在的軟件已經(jīng)是n*365*24小時的運行了,軟件的健壯已經(jīng)是一個很要考慮
2、的時候了。自序:對寫程序來說異常真的是很重要,一個穩(wěn)健的代碼不是靠返回ErrorMessage/return Value來解決的,可是往往我們從C走過來,習慣了這樣的方式。僅 以本文獻給今天將要來臨的流星雨把,還好我能在今天白天把這寫完,否則會是 第4個通宵了;同時感謝Jeffrey大師,沒有他的SEH理論這篇文章只能完成一半,而且所有SEH列子的構(gòu)想都來自他的指導;另外要感謝 Scott Meyers 大師,我是 看他的書長大的;還要感謝Adamc / Darwin / Julian,當然還有 Nick的Coffee內(nèi)容導讀:(請打開文檔結(jié)構(gòu)圖來讀這篇文章。)本文包括2個大的異常實現(xiàn)概念:C
3、+的標準異常和 SHE異常。C+ 標準異常:也許我們了解過他,但你有考慮過,其實你根本不會使用,你不相 信,那我問你:垃圾回收在C+中怎么實現(xiàn)?其實不需要實現(xiàn), C+已經(jīng)有了,但是你不會用,那么從 <構(gòu)造和析構(gòu)中的異常拋出 >開始看把。也許很高興看到錯誤之 后的Heap/Stack中對象被釋放,可是如果沒有呢?有或者試想一下一個能解決的錯誤,需要我們把整個程序Kill掉嗎?在C+標準異常中我向你推薦這幾章:< 使用異常規(guī)格編程 > < 構(gòu)造和析構(gòu)中的異常拋出 > <使用析構(gòu)函數(shù)防止資源泄漏 > 以及一個深點的 <拋出一個異 常的行為>
4、SHE異常:我要問你你是一個 WIN32程序員嗎?如果不是,那么也許你真的不需要看這 塊內(nèi)容了,SHE是Windows 的結(jié)構(gòu)化異常,每一個 WIN32程序員都應該要掌 握它。SHE 功能強大,包括 Termination handling和 Exception handling兩大部分,強有力的維護了代碼的健壯,雖然要以部分系統(tǒng)性能做犧牲(其實可以避免)。在SHE中有大量的代碼,已經(jīng)在 Win平臺上測試過 了。這里要提一下:在 _fin ally 處理中編譯器參與了絕大多數(shù)的工作,而Exception 則是OS接管了幾乎所有的工作,也許我沒有提到的是:對_finally 來說當遇到ExitT
5、hread/ExitProcess/abort等函數(shù)時,fin ally 塊不會被執(zhí)行。另,我們的代碼使用軟件異常是比 return error message 好2*32的方法。另,使用析構(gòu)函數(shù)防止資源泄漏這個節(jié)點引用了 More effective C+ 的條款9 ,用2個列子,講述了我們一般都會犯下的錯誤,往往這種錯誤是我們沒有意識到的但確實是會給我們的軟件帶來致命的Leak/Crash ,但這是有解決的方法的,那就是使用靈巧指針llo如果對照<More effective C+> 的37條條款,關(guān)于異常的高級使用,有以下內(nèi)容是沒有完成的:l使用構(gòu)造函數(shù)防止資源Leak (
6、More effective C+ #10)l禁止異常信息傳遞到析構(gòu)Function 夕卜(More effective C+ #11)l通過引用捕獲異常(More effective C+ #13)I謹慎使用異常規(guī)格(More effective C+ #14)l 了解異常處理造成的系統(tǒng)開銷(More effective C+ #15)l 限制對象數(shù)量 (More effective C+ #26)l 靈巧指針(More effective C+ #28)聲明:節(jié)點: < 使用析構(gòu)函數(shù)防止資源泄漏 > 和 節(jié)點: < 拋出一個異常的行為 > 中 有大量的關(guān)于 More
7、 effective C+的條款,所以本文擋只用于自我閱讀和內(nèi)部交流,任何公開化和商業(yè)化,事先聲明與本人無關(guān)。C+異常C+引入異常的原因C+ 新增的異常機制改變了某些事情,這些改變是徹底的,但這些改變也可能讓我 們不舒服。例如使用未經(jīng)處理的pointer 變的很危險,Memory/Resource Leak變的更有可能了(別說什么 Memory 便宜了,那不是一個優(yōu)秀的程序員說的話。), 寫出一個具有你希望的行為的構(gòu)造函數(shù)和析構(gòu)函數(shù)也變的困難(不可預測),當然最危險的也許是我們寫出的東東狗屁了,或者是速度變慢了。大 多數(shù)的程序員知道 Howto use exception來處理我們的代碼,可是
8、很多人并不是很重視異常的處理(國外的很多Code倒是處理的很好,Java的Exception 機制很不錯)。異常處理機制是解決某些問題的上佳辦法,但同時它也引入了許多隱藏的控制流程;有時候,要正確無誤的使用它并不容易。在異常被throw后,沒有一個方法能夠做到使軟件的行為具有可預測性和可靠性(這句話不是我說的,是 Jack Reeves 寫的 Coping with Exception 和 Herb Sutter 寫的Excepti on-Safe Generic Con tai ners中的。)一個沒有按照異常安全設(shè)計的程序想Run正常,是做夢,別去想沒有異常出現(xiàn)的可能,對C程序來說,使用E
9、rror Code 就可以了,為什么還要引入異常?因為異常不能被 忽略。如果一個函數(shù)通過設(shè)置一個狀態(tài)變量或返回錯誤代碼來表示一個異常狀態(tài),沒 有辦法保證函數(shù)調(diào)用者將一定檢測變量或測試錯誤代碼。結(jié)果程序會從它遇到的異常狀態(tài)繼續(xù)運行,異常沒有被捕獲,程序立即會終止執(zhí)行。在 C程序中,我們可以用int setjmp( jmp_buf env );和voidIongjmp( jmp_buf env, int value );這2個函數(shù)來完成和異常處理相識的功能,但是MSDN中介紹了在C+中使用longjmp 來調(diào)整stack時不能夠?qū)植康膶ο?調(diào)用析構(gòu)函數(shù),但是對C+程序來說,析構(gòu)函數(shù)是重要的(我就
10、一般都把對象的Delete 放在析構(gòu)函數(shù)中)。所以我們需要一個方法:能夠通知異常狀態(tài),又不能忽略這個通知,并且Searchi ng the stack以便找到異常代碼時,還要確保局部對象的析構(gòu)函數(shù)被Call 。而C+的異常處理剛好就是來解決這些問題的。有的地方只有用異常才能解決問題,比如說,在當前上下文環(huán)境中,無法捕捉或確 定的錯誤類型,我們就得用一個異常拋出到更大的上下文環(huán)境當中去。還有,異常處 理的使用呢,可以使出錯處理程序與通常I代碼分離開來,使代碼更簡潔更靈活。另外就是程序必不可少的健壯性了,異常處理往往在其中扮演著重要的角色。C+使用throw 關(guān)鍵字來產(chǎn)生異常,try關(guān)鍵字用來檢測
11、的程序塊,catch關(guān)鍵字用 來填寫異常處理的代碼。異??梢杂梢粋€確定類或派生類的對象產(chǎn)生。C+能釋放堆棧,并可清除堆棧中所有的對象。C+的異常和pascal不同,是要程序員自己去實現(xiàn)的,編譯器不會做過多的動作。throw異常類編程拋出異常用throw ,女口:throw Excepti on Class( my throw );例句中,Exceptio nClass是一個類,它的構(gòu)造函數(shù)以一個字符串做為參數(shù)。也就是說,在throw 的時候,C+的編譯器先構(gòu)造一個ExceptionClass的對象,讓它作為throw 的值拋出去。同時,程序返回,調(diào)用析構(gòu)??聪旅孢@個程序:#in clude &
12、lt;iostream.h>class Exceptio nClasschar* n ame;public:Excepti on Class(c onst char* n ame="default n ame")cout<<"C on struct "<<n ame<<e ndl;this->n ame=n ame;Excepti on Class()cout<<"Destruct "< <n ame<<e ndl;void mythrow()thro
13、w Exceptio nClass("my throw"); void mai n()Excepti on Class e("Test");trye.mythrow();catch(.)cout<< II *| <<endl;這是輸出信息:Con struct TestCon struct my throwDestruct my throw*Destruct my throw(這里是異常處理空間中對異常類的拷貝的析構(gòu))Destruct Test不過一般來說我們可能更習慣于把會產(chǎn)生異常的語句和要throw的異常類分成不同的類來寫,下
14、面的代碼可以是我們更愿意書寫的:class Exceptio nClasspublic:Excepti on Class(c onst char* n ame="Excepti on Default Class") cout<<"Excepti on Class Con struct Strin g"<<e ndl;Excepti on Class()cout<<"Exception Class Destruct String"<<endl;void ReportError() cout
15、<<"Excepti on Class: This is Report Error Message"<<e ndl; ;class ArguClasschar* n ame;public:ArguClass(char* n ame="default n ame")cout<<"C on struct Strin g:"< <n ame<<e ndl;this->n ame=n ame;ArguClass()cout<<"Destruct Strin
16、 g:"< <n ame<<e ndl;void mythrow()void Exceptio nFun ctio n(argume nt)throw()throw Exceptio nClass("my throw");; _tmai n()ArguClass e("haha");try e.mythrow();catch(i nt)cout<<"lf This is Message display scree n. This is a Error!"<<e ndl;catc
17、h(Excepti on Class pTest)pTest.ReportError();catch(.) cout<<'朱*"<<e ndl;輸出 MessageCon struct Strin g:hahaExcepti on Class Con struct StringExcepti on Class Destruct StringExcepti on Class: This is Report Error MessageExcepti on Class Destruct StringDestruct Strin g:haha使用異常規(guī)格編程如
18、果我們調(diào)用別人的函數(shù),里面有異常拋出,用去查看它的源代碼去看看都有什么異 常拋出嗎?這樣就會很煩瑣。比較好的解決辦法,是編寫帶有異常拋出的函數(shù)時,采 用異常規(guī)格說明,使我們看到函數(shù)聲明就知道有哪些異常出現(xiàn)。異常規(guī)格說明大體上為以下格式:void Exceptio nFun ctio n(argume ntthro>w(Exceptio nClass1,ExceptionClass2,.)所有異常類都在函數(shù)末尾的throw()的括號中得以說明了,這樣,對于函數(shù)調(diào)用者來說,是一清二楚的。注意下面一種形式: 表明沒有任何異常拋出。而正常的void ExceptionFunction(argum
19、ent)則表示:可能拋出任何一種異常,當然,也可能沒有異常,意義是最廣泛的。異常捕獲之后,可以再次拋出,就用一個不帶任何參數(shù)的throw 語句就可以了。構(gòu)造和析構(gòu)中的異常拋出這是異常處理中最要注意的地方了先看個程序,假如我在構(gòu)造函數(shù)的地方拋出異常,這個類的析構(gòu)會被調(diào)用嗎?可如果 不調(diào)用,那類里的東西豈不是不能被釋放了?#in clude <iostream.h>#in clude <stdlib.h>class Exceptio nClass1char* s;public:Exceptio nClass1()cout<<"Excepti on Cl
20、ass1()"<<e ndl;s=new char4;cout<<"throw a excepti on"<<en dl;throw 18;Exceptio nClass1()cout<<"Excepti on Class1()"<<e ndl;delete s;void mai n()tryExcepti on Class1 e;catch(.)結(jié)果為:Excepti on Class1() throw a excepti on在這兩句輸出之間,我們已經(jīng)給S分配了內(nèi)存,但內(nèi)存沒有被釋
21、放(因為它是在析構(gòu)函數(shù)中釋放的)。應該說這符合實際現(xiàn)象,因為對象沒有完整構(gòu)造。為了避免這種情況,我想你也許會說:應避免對象通過本身的構(gòu)造函數(shù)涉及到異常拋 出。即:既不在構(gòu)造函數(shù)中出現(xiàn)異常拋出,也不應在構(gòu)造函數(shù)調(diào)用的一切東西中出現(xiàn) 異常拋出。但是在C+中可以在構(gòu)造函數(shù)中拋出異常,經(jīng)典的解決方案是使用STL的標準類auto_ptr 。其實我們也可以這樣做來實現(xiàn):在類中增加一個Init();以及UnInit();成員函數(shù)用于進行容易產(chǎn)生錯誤的資源分配工作,而真正的構(gòu)造函數(shù)中先將所有成員置為NULL,然后調(diào)用Init(); 并判斷其返回值/或者捕捉Init()拋出的異常,如果Init(); 失敗了,則
22、在構(gòu)造函數(shù)中調(diào)用 UnInit(); 并設(shè)置一個標志位表明構(gòu)造失敗。UnInit() 中按照成員是否為 NULL進行資源的釋放工作。那么,在析構(gòu)函數(shù)中的情況呢?我們已經(jīng)知道,異常拋出之后,就要調(diào)用本身的析構(gòu) 函數(shù),如果這析構(gòu)函數(shù)中還有異常拋出的話,則已存在的異常尚未被捕獲,會導致異 常捕捉不到。標準C+異常類C+有自己的標準的異常類。 一個基類:exception 是所有C+異常的基類。class exceptio n public:excepti on() throw();excepti on(const excepti on& rhs) throw();excepti on&
23、; operator=(c onst excepti on& rhs) throw();virtual excepti on() throw();virtual const char *what() const throw(); 下面派生了兩個異常類:logic_erro報告程序的邏輯錯誤,可在程序執(zhí)行前被檢測到。run time_erro報告程序運行時的錯誤,只有在運行的時候才能檢測到。以上兩個又分別有自己的派生類:由logic_erro 派生的異常類domain error報告違反了前置條件in valid_argume nt指出函數(shù)的一個無效參數(shù)length_error指出有一個
24、產(chǎn)生超過 NPOS 長度的對象的企圖(NPOS為size_t的最大可表現(xiàn)值out_of_ra nge報告參數(shù)越界bad_cast在運行時類型識別中有一個無效的dyn amic_cast 表達式bad_typeid報告在表達式typeid(*p)中有一個空指針 P 由runtime_error 派生的異常ran ge_error報告違反了后置條件overflow_error 報告一個算術(shù)溢出bad_alloc報告一個存儲分配錯誤使用析構(gòu)函數(shù)防止資源泄漏這部分是一個經(jīng)典和很平常就會遇到的實際情況,下面的內(nèi)容大部分都是從MoreEffective C+條款中得到的。假 設(shè),你正在為一個小動物收容所編
25、寫軟件,小動物收容所是一個幫助小狗小貓尋 找主人的組織。每天收容所建立一個文件,包含當天它所管理的收容動物的資料信 息,你的工作是寫一個程序讀出這些文件然后對每個收容動物進行適當?shù)奶幚?appropriate process ing )。完成這個程序一個合理的方法是定義一個抽象類,ALA( "Adorable Little Animal"),然后為小狗和小貓建立派生類。一個虛擬函數(shù)processAdopti on分別對各個種類的動物進行處理:class ALA public:virtual void processAdoptio n() = 0;class Puppy: p
26、ublic ALA public:processAdopti ons沒有捕獲異常,所以異常將傳遞給processAdoptio ns 的調(diào)用virtual void processAdoptio n();;class Kitte n: public ALA public:virtual void processAdoptio n();你需要一個函數(shù)從文件中讀信息,然后根據(jù)文件中的信息產(chǎn)生一個 puppy (小狗)對象或者kitten(小貓)對象。這個工作非常適合于虛擬構(gòu)造器 (virtual constructor),在條款25詳細描述了這種函數(shù)。為了完成我們的目標,我們這樣聲明函數(shù): 從s中
27、讀動物信息,然后返回一個指針/指向新建立的某種類型對象ALA * readALA(istrea m& s);你的程序的關(guān)鍵部分就是這個函數(shù),如下所示:/還有數(shù)據(jù)時,繼續(xù)循環(huán)file:/得到下一個動物file:/處理收容動物file:/刪除readALA返回的對象void processAdopti on s(istream& dataSource) while (dataSource) ALA *pa = readALA(dataSource); pa->processAdopti on();delete pa; 這個函數(shù)循環(huán)遍歷 dataSource 內(nèi)的信息,處理它所
28、遇到的每個項目。 唯一要記住的 一點是在每次循環(huán)結(jié)尾處刪除 ps。這是必須的,因為每次調(diào)用 readALA都建立一個 堆對象。如果不刪除對象,循環(huán)將產(chǎn)生資源泄漏。現(xiàn)在考慮一下,如果 pa->processAdoption拋出了一個異常,將會發(fā)生什么?者。轉(zhuǎn)遞中,processAdoptio ns函數(shù)中的調(diào)用 pa->processAdopti on語句后的所有語句都被跳過,這就是說pa沒有被刪除。結(jié)果,任何時候pa->processAdoptio n拋出一個異常都會導致processAdoptio ns內(nèi)存泄漏。堵塞泄漏很容易,void processAdopti on s(
29、istream& dataSource)while (dataSource) ALA *pa = readALA(dataSource);try pa->processAdopti on();catch (.) /捕獲所有異常delete pa;/避免內(nèi)存泄漏/當異常拋出時throw;/傳送異常給調(diào)用者delete pa;/避免資源泄漏/當沒有異常拋出時但是你必須用try和catch對你的代碼進行小改動。更重要的是你必須寫雙份清除 代碼,一個為正常的運行準備,一個為異常發(fā)生時準備。在這種情況下,必須寫兩個delete代碼。象其它重復代碼一樣,這種代碼寫起來令人心煩又難于維護,而且
30、 它看上去好像存在著問題。不論我們是讓processAdoptio ns正常返回還是拋出異常,我們都需要刪除 pa,所以為什么我們必須要在多個地方編寫刪除代碼呢?我們可以把總被執(zhí)行的清除代碼放入processAdoptions函數(shù)內(nèi)的局部對象的析構(gòu)函數(shù)里,這樣可以避免重復書寫清除代碼。因為當函數(shù)返回時局部對象總是被釋放,無論函數(shù)是如何退出的。(僅有一種例外就是當你調(diào)用Iongjmp 時。Longjmp 的這個缺點是C+率先支持異常處理的主要原因)具體方法是用一個對象代替指針pa,這個對象的行為與指針相似。當pointer-like(類指針)對象被釋放時,我們能讓它的析構(gòu)函數(shù)調(diào)用delete。替
31、代指針的對象被稱為smart pointers(靈巧指針),下面有解釋,你能使得pointer-like對象非常靈巧。在這里,我們用不著這么聰明的指針,我們只需要一個poi nter-lik對象,當它離開生存空間時知道刪除它指向的對象。寫出這樣一個類并不困難,但是我們不需要自己去寫。標準C+庫函數(shù)包含一個類模板,叫做auto_ptr ,這正是我們想要的。每一個auto_ptr 類的 構(gòu)造函數(shù)里,讓一個指針指向一個堆對象(heap object ),并且在它的析構(gòu)函數(shù)里刪除這個對象。 下面所示的是auto_ptr類的一些重要的部分:template<class T>class aut
32、o_ptr public:/保存ptr,指向?qū)ο?刪除ptr指向的對象/ raw ptr to objectauto_ptr(T *p = 0): ptr(p) auto_ptr() delete ptr; private:T *ptr;auto_ptr類的完整代碼是非常有趣的,上述簡化的代碼實現(xiàn)不能在實際中應用。(我們至少必須加上拷貝構(gòu)造函數(shù),賦值operator以及下面將要講到的poi nter-emulati ng函數(shù)),但是它背后所蘊含的原理應該是清楚的:用auto_ptr對象代替raw指針,你將不再為堆對象不能被刪除而擔心,即使在拋出異常時,對象也能被及時刪除。(因為auto_ptr
33、的析構(gòu)函數(shù)使用的是單對象形式的delete,所以auto_ptr 不能用于指向?qū)ο髷?shù)組的指針。如果想讓auto_ptr類似于一個數(shù)組模板,你必須自己寫一個。在這種情況下,用vector代替array可能更好) auto_ptrtemplate<class T>class auto_ptr public:typedef T eleme nt_type;explicit auto_ptr(T *p = 0) throw();auto_ptr(c onst auto_ptr<T >& rhs) throw(); auto_ptr<T >& oper
34、ator=(auto_ptr<T>& rhs) throw(); auto_ptr();T& operator*() const throw();T *operator->() const throw();T *get() const throw();T *release() const throw();;使用 auto_ptr對象代替 raw 指針,processAdoptions女口下所示:void processAdopti on s(istream& dataSource)while (dataSource) auto_ptr<ALA&g
35、t; pa(readALA(dataSource);pa->processAdopti on();這個版本的processAdoptions在兩個方面區(qū)別于原來的processAdoptions函數(shù)。第一,pa被聲明為一個 auto_ptr<ALA>對象,而不是一個 raw ALA* 指針。第二,在循環(huán)的結(jié)尾沒有 delete語句。其余部分都一樣,因為除了析構(gòu)的方式,auto_ptr 對象的行為就象一個普通的指針。是不是很容易。隱藏在auto_ptr后的思想是:用一個對象存儲需要被自動釋放的資源,然后依靠對象的析構(gòu)函數(shù)來釋放資源,這種思想不只是可以運用在指針上,還能用在其它資
36、源的分配和釋放上。想一下這樣一個在GUI程序中的函數(shù),它需要建立一個window 來顯式一些信息:/這個函數(shù)會發(fā)生資源泄漏,如果一個異常拋出void display In fo(c onst In formatio n& info)WINDOW_HANDLE w(createWi ndow();在w對應的window中顯式信息destroyWi ndow(w);很 多window 系統(tǒng)有 C like 接口,使用象like createWindow和destroyWindow函數(shù)來獲取和釋放 window 資源。如果在 w對應的 window 中顯示信息時,一個異常被拋出,w所對應的w
37、indow 將被丟失,就象其它動態(tài)分配的資源一樣。解決方法與前面所述的一樣,建立一個類,讓它的構(gòu)造函數(shù)與析構(gòu)函數(shù)來獲取和釋放資源:file:/ 一個類,獲取和釋放一個 win dow 句柄class Win dowHa ndle public:Win dowHa ndle(WINDOW_HANDLE han dle): w(ha ndle) Win dowHa ndle() destroyWi ndow(w); operator WINDOW_HANDLE() return w; / see belowprivate:WINDOW_HANDLE w;II下面的函數(shù)被聲明為私有,防止建立多個WI
38、NDOW_HANDLE 拷貝file:/有關(guān)一個更靈活的方法的討論請參見下面的靈巧指針Win dowHa ndle(co nst Win dowHa ndle &);Win dowHa ndle & operator* onst Win dowHa ndle&);這看上去有些象auto_ptr ,只是賦值操作與拷貝構(gòu)造被顯式地禁止(參見 More effective C+ 條款27 ),有一個隱含的轉(zhuǎn)換操作能把 WindowHandle 轉(zhuǎn)換為WINDOW_HANDLE。這個能力對于使用WindowHandle對象非常重要,因為這意味著你能在任何地方象使用raw WIN
39、DOW_HANDLE 一樣來使用WindowHandle。(參見 More effective C+ 條款5,了解為什么你應該謹慎使用隱式類型轉(zhuǎn)換操作)通過給出的 WindowHandle類,我們能夠重寫 displayInfo函數(shù),如下所示:/如果一個異常被拋出,這個函數(shù)能避免資源泄漏void display In fo(c onst In formatio n& info)Win dowHa ndle w(createWi ndow();在w對應的window中顯式信息即使一個異常在 displaylnfo內(nèi)被拋出,被createWindow 建立的window 也能被釋放。資源應
40、該被封裝在一個對象里,遵循這個規(guī)則,你通常就能避免在存在異常環(huán)境里 發(fā)生資源泄漏。但是如果你正在分配資源時一個異常被拋出,會發(fā)生什么情況呢?例 如當你正處于resource-acquiri ng類的構(gòu)造函數(shù)中。還有如果這樣的資源正在被釋放時,一個異常被拋出,又會發(fā)生什么情況呢?構(gòu)造函數(shù)和析構(gòu)函數(shù)需要特殊的技術(shù)。你能在 More effective C+ 條款 10 和 More effective C+ 條款 11 中獲取 有關(guān)的知識。拋出一個異常的行為個人認為接下來的這部分其實說的很經(jīng)典,對我們理解異常行為/異??截愂呛苡袔椭?。條款12 :理解拋出一個異常I與傳遞一個參數(shù)I或調(diào)用一個虛函數(shù)
41、I間的差異從語法上看,在函數(shù)里聲明參數(shù)與在catch子句中聲明參數(shù)幾乎沒有什么差別:class Widget . ;/void f1(Widget w);void f2(Widget & w);void f3(co nst Widget& w);void f4(Widget *pw);void f5(co nst Widget *pw);catch (Widget w).catch (Widget& w).catch (const Widget & w).catch (Widget *pw) .catch (const Widget *pw) .file:/ 一
42、個類,具體是什么類在這里并不重要/ 一些函數(shù),其參數(shù)分別為/ Widget, Widget&,或/ Widget* 類型file:/ 一些catch 子句,用來file:/捕獲異常,異常的類型為/ Widget, Widget&, 或/ Widget*你因此可能會認為用 throw拋出一個異常到catch子句中與通過函數(shù)調(diào)用傳遞一個 參數(shù)兩者基本相同。這里面確有一些相同點,但是他們也存在著巨大的差異。讓我們先從相同點談起。你傳遞函數(shù)參數(shù)與異常的途徑可以是傳值、傳遞引用或傳 遞指針,這是相同的。但是當你傳遞參數(shù)和異常時,系統(tǒng)所要完成的操作過程則是完 全不同的。產(chǎn)生這個差異的原因是
43、:你調(diào)用函數(shù)時,程序的控制權(quán)最終還會返回到函 數(shù)的調(diào)用處,但是當你拋出一個異常時,控制權(quán)永遠不會回到拋出異常的地方。有這樣一個函數(shù),參數(shù)類型是Widget,并拋出一個 Widget類型的異常:/ 一個函數(shù),從流中讀值到Widget中istream operator»(istrea m& s, Widget& w);void passA ndThrowWidget()Widget localWidget;cin >> localWidget;file:/ 傳遞 localWidget至U operatorthrow localWidget;/ 拋出 loca
44、lWidget 異常當 傳遞localWidget至U函數(shù)operator里,不用進行拷貝操作,而是把operator>> 內(nèi)的引用類型變量 w指向localWidget ,任何對w的操作實際上都施 加到localWidget上。這與拋出localWidget異常有很大不同。不論通過傳值捕獲異 常還是通過引用捕獲(不能通過指針捕獲這個異常,因為類型不匹配)都將進行 lcalWidget的拷貝操作,也就說傳遞到 catch子句中的是 localWidget的拷貝。必須這么做,因為當localWidget離開了生存空間后,其析構(gòu)函數(shù)將被調(diào)用。如果把localWidget 本身(而 不是
45、它的拷貝)傳遞給 catch子句,這個子句接收到的只是 一個被析構(gòu)了的 Widget , 一個Widget的尸體II。這是無法使用的。因此C+規(guī)范要求被做為異常拋出的對象必須被復制。即使被拋出的對象不會被釋放,也會進行拷貝操作。例如如果 passA ndThrowWidget函數(shù)聲明localWidget為靜態(tài)變量(static ),void passA ndThrowWidget()file:/現(xiàn)在是靜態(tài)變量(static );直存在至程序結(jié)束cin >> localWidget;throw localWidget;當拋出異常時仍將復制出file:/localWidget常,也不
46、能在 catch 塊中修改localWidget異常對象進行強制復制拷貝,差異:拋出異常運行速度比參數(shù)傳遞要慢。/象以前那樣運行/ 仍將對 localWidget 進行拷貝操作的一個拷貝。這表示即使通過引用來捕獲異;僅僅能修改 localWidget 的拷貝。對這個限制有助于我們理解參數(shù)傳遞與拋出異常的第二個static Widget localWidget;有這樣一個函數(shù),參數(shù)類型是Widget,并拋出一個 Widget類型的異常:有這樣一個函數(shù),參數(shù)類型是Widget,并拋出一個 Widget類型的異常:usidc52010-09-19 00:08當異常對象被拷貝時,拷貝操作是由對象的 拷
47、貝構(gòu)造函數(shù)完成的。該拷貝 構(gòu)造函數(shù)是對象的靜態(tài)類型(static type )所對應類的拷貝構(gòu)造函數(shù),而這個事實影響到你如何在catch塊中再拋出一個catch 塊,乍一看好像一樣:catch (Widget & w)/捕獲Widget異常/throw;catch (Widget & w)/處理異常/重新拋出異常,讓它繼續(xù)傳遞/捕獲Widget異常/處理異常/傳遞被捕獲異常的 拷貝throw w;這兩個catch塊的差別在于第一個catch塊中重新拋出的是當前捕獲的異 常,而第二個catch塊中重新拋出的是當前捕獲異常的一個新的拷貝 果忽略生成額外拷貝的系統(tǒng)開銷,這兩種方法還有
48、差異么? 當然有。第一個塊中重新拋出的是當前異常(current exception 它是什么類型。特別是如果這個異常開始就是做為SpecialWidget出的,那么第一個塊中傳遞出去的還是 靜態(tài)類型(static type )是 Widget 。/),無論 類型拋SpecialWidget 異常,即使 w的 這是因為重新拋出異常時沒有進行不是對象的動態(tài)類型(dynamic type)對應類的拷貝構(gòu)造函數(shù)。比如以下這經(jīng)過少許修改的passAndThrowWidget:class Widget . ;class SpecialWidget: public Widget . ;void passA
49、 ndThrowWidget()SpecialWidget localSpecialWidget;Widget& rw = localSpecialWidget;/ rw 引用 SpecialWidgetthrow rw;file:/ 它拋出一個類型為 Widget/的異常 這 里拋出的異常對象是 Widget,即使rw引用的是一個SpecialWidget 因為 rw 的靜態(tài)類型(static type )是 Widget,而不是 SpecialWidget 。 你的編譯器根本沒有主要到rw引用的是一個SpecialWidget 。編譯器所 注意的是rw的靜態(tài)類型(static ty
50、pe )。這種行為可能與你所期待的不 一樣,但是這與在其他情況下C+中拷貝構(gòu)造函數(shù)的行為是一致的。(不 過有一種技術(shù)可以讓你根據(jù)對象的動態(tài)類型dynamic type進行拷貝,參見條款25 異常是其它對象的拷貝, 異常。比如下面這兩個拷貝操作。第二個catch塊重新拋出的是新異常,類型總是Widget ,因為w的靜態(tài)類型(static type )是Widget。一般來說,你應該用throw來重新拋出當前的異常,因為這樣不會改變被傳遞出去的異常類型,而且 更有效率,因為不用生成一個新拷貝。(順便說一句,異常生成的拷貝是一個臨時對象。正如條款19解釋的,),拋出的:/通過傳值捕獲異常/通過傳遞引
51、用捕獲異常file:/通過傳遞指向const的引用臨時對象能讓編譯器優(yōu)化它的生存期(optimize it out of existe nee 不過我想你的編譯器很難這么做,因為程序中很少發(fā)生異常,所以編譯器 廠商不會在這方面花大量的精力。) 讓我們測試一下下面這三種用來捕獲 Widget異常的catch子句,異常是 做為 passAndThrowWidgetp catch (Widget w)./catch (Widget & w).catch (const Widget & w).file:/捕獲異常我 們立刻注意到了傳遞參數(shù)與傳遞異常的另一個差異。一個被異常拋出的對象(剛
52、才解釋過,總是一個臨時對象)可以通過普通的引用捕獲;它不 需要通過指向 const對象的引用(reference-to-const)捕獲。在函數(shù)調(diào)用中不允許轉(zhuǎn)遞一個臨時對象到一個非const引用類型的參數(shù)里(參見條款19 ),但是在異常中卻被允許。讓我們先不管這個差異,回到異常對象拷貝的測試上來。我們知道當用傳 值的方式傳遞函數(shù)的參數(shù),我們制造了被傳遞對象的一個拷貝(參見Effective C+ 條款22 ),并把這個拷貝存儲到函數(shù)的參數(shù)里。同樣我 們通過傳值的方式傳遞一個異常時,也是這么做的。當我們這樣聲明一個 catch子句時:catch (Widget w) ./ 通過傳值捕獲會建立兩個
53、被拋出對象的拷貝,一個是所有異常都必須建立的臨時對象, 第二個是把臨時對象拷貝進 w中。同樣,當我們通過引用捕獲異常時, catch (Widget& w) ./ 通過引用捕獲catch (const Widget& w) .file:/ 也通過引用捕獲這仍舊會建立一個被拋出對象的拷貝:拷貝是一個臨時對象。相反當我們 通過引用傳遞函數(shù)參數(shù)時,沒有進行對象拷貝。當拋出一個異常時,系統(tǒng) 構(gòu)造的(以后會析構(gòu)掉)被拋出對象的拷貝數(shù)比以相同對象做為參數(shù)傳遞 給函數(shù)時構(gòu)造的拷貝數(shù)要多一個。我 們還沒有討論通過指針拋出異常的情況,不過通過指針拋出異常與通過指針傳遞參數(shù)是相同的。不論哪種方法都
54、是一個指針的拷貝被傳遞。你不 能認為拋出的指針是 一個指向局部對象的指針,因為當異常離開局部變量 的生存空間時,該局部變量已經(jīng)被釋放。Catch子句將獲得一個指向已經(jīng)不存在的對象的指針。這種行為在設(shè)計時應該予以避免。對象從函數(shù)的調(diào)用處傳遞到函數(shù)參數(shù)里與從異常拋出點傳遞到 catch子句 里所采用的方法不同,這只是參數(shù)傳遞與異常傳遞的區(qū) 別的一個方面,第 二個差異是在函數(shù)調(diào)用者或拋出異常者與被調(diào)用者或異常捕獲者之間的類型匹配的過程不同。比如在標準數(shù)學庫(the sta ndard math library中sqrt函數(shù):double sqrt(double);/ from <cmath&g
55、t; or <math.h>我們能這樣計算一個整數(shù)的平方根,如下所示:int i;double sqrtOfi = sqrt(i);毫無疑問,C+允許進行從int到double的隱式類型轉(zhuǎn)換,所以在sqrt 的調(diào)用中,i被悄悄地轉(zhuǎn)變?yōu)閐ouble類型,并且其返回值也是double 。(有關(guān)隱式類型轉(zhuǎn)換的詳細討論參見條款5) 一般來說,catch子句匹配異常類型時不會進行這樣的轉(zhuǎn)換。見下面的代碼:void f(i nt value)/ 如果 someFunction()返回 file:/真,拋出一個整形值try if (someF unction() throw value;/只處理
56、double類型的異常catch (double d) 在try塊中拋出的int異常不會被處理double異常的catch子句捕獲。該 子句只能捕獲真真正正為double類型的異常;不進行類型轉(zhuǎn)換。因此如 果要想捕獲int異常,必須使用帶有int或int&參數(shù)的catch子句。不過在catch子句中進行異常匹配時可以進行兩種類型轉(zhuǎn)換。第一種是繼 承類與基類間的轉(zhuǎn)換。一個用來捕獲基類的catch子句也可以處理派生類類型的異常。例如在標準C+庫(STL )定義的異常類層次中的診斷部 分(diagnostics portion)(參見 Effective C+ 條款 49)。捕獲runtime_errors 異常的Catch子句可以捕獲range_error 類型和 overflow_error 類型的異常,可以接收根類 exception 異常的catch子
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 收股份合同模板
- 2025年度特色主題飯店出租運營合同
- 二零二五年度住宅租賃合同終止換房協(xié)議
- 二零二五年度私募基金私下股份轉(zhuǎn)讓協(xié)議書合同
- 2025年度育兒嫂上戶責任保險合同模板
- 二零二五年度企業(yè)風險管理顧問服務合同范本
- 2025年度煙草店店鋪轉(zhuǎn)讓及市場拓展合作合同
- 2025年度車輛碰撞事故免責條款合同范本
- 2025年度私人酒店客房部主管勞動合同
- 2025年代理商品銷售政策合同
- 2025年人教五四新版八年級物理上冊階段測試試卷含答案
- 2025年春季1530安全教育記錄主題
- 礦山2025年安全工作計劃
- 2025年包裝印刷項目可行性研究報告
- 企業(yè)融資報告特斯拉成功案例分享
- 給客戶的福利合同(2篇)
- 銷售調(diào)味品工作總結(jié)5篇
- 2024年江蘇省勞動合同條例
- 供電企業(yè)輿情的預防及處置
- 【高中語文】《氓》課件++統(tǒng)編版+高中語文選擇性必修下冊
- T-WAPIA 052.3-2023 無線局域網(wǎng)設(shè)備技術(shù)規(guī)范 第3部分:接入點和控制器
評論
0/150
提交評論