More Effective C++:通過引用捕獲異常_Web服務(wù)器教程
教程Tag:暫無Tag,歡迎添加,賺取U幣!
當(dāng)你寫一個(gè)catch子句時(shí),必須確定讓異常通過何種方式傳遞到catch子句里。你可以有三個(gè)選擇:與你給函數(shù)傳遞參數(shù)一樣,通過指針(by pointer),通過傳值(by value)或通過引用(by reference)。
我們首先討論通過指針方式捕獲異常(catch by pointer)。從throw處傳遞一個(gè)異常到catch子句是一個(gè)緩慢的過程,在理論上這種方法的實(shí)現(xiàn)對(duì)于這個(gè)過程來說是效率最高的。因?yàn)樵趥鬟f異常信息時(shí),只有采用通過指針拋出異常的方法才能夠做到不拷貝對(duì)象,例如:
這看上去很不錯(cuò),但是實(shí)際情況卻不是這樣。為了能讓程序正常運(yùn)行,程序員定義異常對(duì)象時(shí)必須確保當(dāng)程序控制權(quán)離開拋出指針的函數(shù)后,對(duì)象還能夠繼續(xù)生存。全局與靜態(tài)對(duì)象都能夠做到這一點(diǎn),但是程序員很容易忘記這個(gè)約束。如果真是如此的話,他們會(huì)這樣寫代碼:
這簡(jiǎn)直糟糕透了,因?yàn)樘幚磉@個(gè)異常的catch子句接受到的指針,其指向的對(duì)象已經(jīng)不再存在。
另一種拋出指針的方法是在建立一個(gè)堆對(duì)象(new heap object):
這避免了捕獲一個(gè)指向已被釋放對(duì)象的指針的問題,但是catch子句的作者又面臨一個(gè)令人頭疼的問題:他們是否應(yīng)該刪除他們接受的指針?如果是在堆中建立的異常對(duì)象,那他們必須刪除它,否則會(huì)造成資源泄漏。如果不是在堆中建立的異常對(duì)象,他們絕對(duì)不能刪除它,否則程序的行為將不可預(yù)測(cè)。該如何做呢?
這是不可能知道的。一些clients可能會(huì)傳遞全局或靜態(tài)對(duì)象的地址,另一些可能轉(zhuǎn)遞堆中建立的異常對(duì)象的地址。通過指針捕獲異常,將遇到一個(gè)哈姆雷特式的難題:是刪除還是不刪除?這是一個(gè)難以回答的問題。所以你最好避開它。
而且,通過指針捕獲異常也不符合C++語言本身的規(guī)范。四個(gè)標(biāo)準(zhǔn)的異常――bad_alloc(當(dāng)operator new(參見條款8)不能分配足夠的內(nèi)存時(shí),被拋出),bad_cast(當(dāng)dynamic_cast針對(duì)一個(gè)引用(reference)操作失敗時(shí),被拋出),bad_typeid(當(dāng)dynamic_cast對(duì)空指針進(jìn)行操作時(shí),被拋出)和bad_exception(用于unexpected異常; 參見條款14)――都不是指向?qū)ο蟮闹羔�,所以你必須通過值或引用來捕獲它們。
通過值捕獲異常(catch-by-value)可以解決上述的問題,例如異常對(duì)象刪除的問題和使用標(biāo)準(zhǔn)異常類型的問題。但是當(dāng)它們被拋出時(shí)系統(tǒng)將對(duì)異常對(duì)象拷貝兩次(參見條款12)。而且它會(huì)產(chǎn)生 slicing problem,即派生類的異常對(duì)象被做為基類異常對(duì)象捕獲時(shí),那它的派生類行為就被切掉了(sliced off)。這樣的sliced對(duì)象實(shí)際上是一個(gè)基類對(duì)象:它們沒有派生類的數(shù)據(jù)成員,而且當(dāng)調(diào)用它們的虛擬函數(shù)時(shí),系統(tǒng)解析后調(diào)用的是基類對(duì)象的函數(shù)。 (當(dāng)一個(gè)對(duì)象通過傳值方式傳遞給函數(shù),也會(huì)發(fā)生一樣的情況――參見Effective C++ 條款22)。例如下面這個(gè)程序采用了擴(kuò)展自標(biāo)準(zhǔn)異常類的異常類層次體系:
調(diào)用的是基類的what函數(shù),即使被拋出的異常對(duì)象是Validation_error和 Validation_error類型,它們已經(jīng)重新定義的虛擬函數(shù)。這種slicing行為絕不是你所期望的。
最后剩下方法就是通過引用捕獲異常(catch-by-reference)。通過引用捕獲異常能使你避開上述所有問題。不象通過指針捕獲異常,這種方法不會(huì)有對(duì)象刪除的問題而且也能捕獲標(biāo)準(zhǔn)異常類型。也不象通過值捕獲異常,這種方法沒有slicing problem,而且異常對(duì)象只被拷貝一次。
我們采用通過引用捕獲異常的方法重寫最后那個(gè)例子,如下所示:
這里沒有對(duì)throw進(jìn)行任何改變,僅僅改變了catch子句,給它加了一個(gè)&符號(hào)。然而這個(gè)微小的改變能造成了巨大的變化,因?yàn)閏atch塊中的虛擬函數(shù)能夠如我們所愿那樣工作了:調(diào)用的Validation_erro函數(shù)是我們重新定義過的函數(shù)。
如果你通過引用捕獲異常(catch by reference),你就能避開上述所有問題,不會(huì)為是否刪除異常對(duì)象而煩惱;能夠避開slicing異常對(duì)象;能夠捕獲標(biāo)準(zhǔn)異常類型;減少異常對(duì)象需要被拷貝的數(shù)目。所以你還在等什么?通過引用捕獲異常吧(Catch exceptions by reference)!
我們首先討論通過指針方式捕獲異常(catch by pointer)。從throw處傳遞一個(gè)異常到catch子句是一個(gè)緩慢的過程,在理論上這種方法的實(shí)現(xiàn)對(duì)于這個(gè)過程來說是效率最高的。因?yàn)樵趥鬟f異常信息時(shí),只有采用通過指針拋出異常的方法才能夠做到不拷貝對(duì)象,例如:
| class exception { ... }; // 來自標(biāo)準(zhǔn)C++庫(STL) // 中的異常類層次 void someFunction() { static exception ex; // 異常對(duì)象 ... throw &ex; // 拋出一個(gè)指針,指向ex ... } void doSomething() { try { someFunction(); // 拋出一個(gè) exception* } catch (exception *ex) { // 捕獲 exception*; ... // 沒有對(duì)象被拷貝 } } |
這看上去很不錯(cuò),但是實(shí)際情況卻不是這樣。為了能讓程序正常運(yùn)行,程序員定義異常對(duì)象時(shí)必須確保當(dāng)程序控制權(quán)離開拋出指針的函數(shù)后,對(duì)象還能夠繼續(xù)生存。全局與靜態(tài)對(duì)象都能夠做到這一點(diǎn),但是程序員很容易忘記這個(gè)約束。如果真是如此的話,他們會(huì)這樣寫代碼:
| void someFunction() { exception ex; // 局部異常對(duì)象; // 當(dāng)退出函數(shù)的生存空間時(shí) // 這個(gè)對(duì)象將被釋放。 ... throw &ex; // 拋出一個(gè)指針,指向 ... // 已被釋放的對(duì)象 } |
這簡(jiǎn)直糟糕透了,因?yàn)樘幚磉@個(gè)異常的catch子句接受到的指針,其指向的對(duì)象已經(jīng)不再存在。
另一種拋出指針的方法是在建立一個(gè)堆對(duì)象(new heap object):
| void someFunction() { ... throw new exception; // 拋出一個(gè)指針,指向一個(gè)在堆中 ... // 建立的對(duì)象(希望 } // 自己不要再拋出一個(gè) // 異常!) |
這避免了捕獲一個(gè)指向已被釋放對(duì)象的指針的問題,但是catch子句的作者又面臨一個(gè)令人頭疼的問題:他們是否應(yīng)該刪除他們接受的指針?如果是在堆中建立的異常對(duì)象,那他們必須刪除它,否則會(huì)造成資源泄漏。如果不是在堆中建立的異常對(duì)象,他們絕對(duì)不能刪除它,否則程序的行為將不可預(yù)測(cè)。該如何做呢?
這是不可能知道的。一些clients可能會(huì)傳遞全局或靜態(tài)對(duì)象的地址,另一些可能轉(zhuǎn)遞堆中建立的異常對(duì)象的地址。通過指針捕獲異常,將遇到一個(gè)哈姆雷特式的難題:是刪除還是不刪除?這是一個(gè)難以回答的問題。所以你最好避開它。
而且,通過指針捕獲異常也不符合C++語言本身的規(guī)范。四個(gè)標(biāo)準(zhǔn)的異常――bad_alloc(當(dāng)operator new(參見條款8)不能分配足夠的內(nèi)存時(shí),被拋出),bad_cast(當(dāng)dynamic_cast針對(duì)一個(gè)引用(reference)操作失敗時(shí),被拋出),bad_typeid(當(dāng)dynamic_cast對(duì)空指針進(jìn)行操作時(shí),被拋出)和bad_exception(用于unexpected異常; 參見條款14)――都不是指向?qū)ο蟮闹羔�,所以你必須通過值或引用來捕獲它們。
通過值捕獲異常(catch-by-value)可以解決上述的問題,例如異常對(duì)象刪除的問題和使用標(biāo)準(zhǔn)異常類型的問題。但是當(dāng)它們被拋出時(shí)系統(tǒng)將對(duì)異常對(duì)象拷貝兩次(參見條款12)。而且它會(huì)產(chǎn)生 slicing problem,即派生類的異常對(duì)象被做為基類異常對(duì)象捕獲時(shí),那它的派生類行為就被切掉了(sliced off)。這樣的sliced對(duì)象實(shí)際上是一個(gè)基類對(duì)象:它們沒有派生類的數(shù)據(jù)成員,而且當(dāng)調(diào)用它們的虛擬函數(shù)時(shí),系統(tǒng)解析后調(diào)用的是基類對(duì)象的函數(shù)。 (當(dāng)一個(gè)對(duì)象通過傳值方式傳遞給函數(shù),也會(huì)發(fā)生一樣的情況――參見Effective C++ 條款22)。例如下面這個(gè)程序采用了擴(kuò)展自標(biāo)準(zhǔn)異常類的異常類層次體系:
| class exception { // 如上,這是 public: // 一個(gè)標(biāo)準(zhǔn)異常類 virtual const char * what() throw(); // 返回異常的簡(jiǎn)短描述. ... // (在函數(shù)聲明的結(jié)尾處 // 的"throw()", }; //有關(guān)它的信息 class runtime_error: //也來自標(biāo)準(zhǔn)C++異常類 public exception { ... }; class Validation_error: // 客戶自己加入個(gè)類 public runtime_error { public: virtual const char * what() throw(); // 重新定義在異常類中 ... //虛擬函數(shù) }; // void someFunction() // 拋出一個(gè) validation { // 異常 ... if (a validation 測(cè)試失敗) { throw Validation_error(); } ... } void doSomething() { try { someFunction(); // 拋出 validation } //異常 catch (exception ex) { //捕獲所有標(biāo)準(zhǔn)異常類 // 或它的派生類 cerr << ex.what(); // 調(diào)用 exception::what(), ... // 而不是Validation_error::what() } } |
調(diào)用的是基類的what函數(shù),即使被拋出的異常對(duì)象是Validation_error和 Validation_error類型,它們已經(jīng)重新定義的虛擬函數(shù)。這種slicing行為絕不是你所期望的。
最后剩下方法就是通過引用捕獲異常(catch-by-reference)。通過引用捕獲異常能使你避開上述所有問題。不象通過指針捕獲異常,這種方法不會(huì)有對(duì)象刪除的問題而且也能捕獲標(biāo)準(zhǔn)異常類型。也不象通過值捕獲異常,這種方法沒有slicing problem,而且異常對(duì)象只被拷貝一次。
我們采用通過引用捕獲異常的方法重寫最后那個(gè)例子,如下所示:
| void someFunction() //這個(gè)函數(shù)沒有改變 { ... if (a validation 測(cè)試失敗) { throw Validation_error(); } ... } void doSomething() { try { someFunction(); // 沒有改變 } catch (exception& ex) { // 這里,我們通過引用捕獲異常 // 以替代原來的通過值捕獲 cerr << ex.what(); // 現(xiàn)在調(diào)用的是 // Validation_error::what(), ... // 而不是 exception::what() } } |
這里沒有對(duì)throw進(jìn)行任何改變,僅僅改變了catch子句,給它加了一個(gè)&符號(hào)。然而這個(gè)微小的改變能造成了巨大的變化,因?yàn)閏atch塊中的虛擬函數(shù)能夠如我們所愿那樣工作了:調(diào)用的Validation_erro函數(shù)是我們重新定義過的函數(shù)。
如果你通過引用捕獲異常(catch by reference),你就能避開上述所有問題,不會(huì)為是否刪除異常對(duì)象而煩惱;能夠避開slicing異常對(duì)象;能夠捕獲標(biāo)準(zhǔn)異常類型;減少異常對(duì)象需要被拷貝的數(shù)目。所以你還在等什么?通過引用捕獲異常吧(Catch exceptions by reference)!
相關(guān)Web服務(wù)器教程:
- 推薦!各類建站程序偽靜態(tài)規(guī)則代碼
- 詳細(xì)的DedeCMS(織夢(mèng))目錄權(quán)限安全設(shè)置教程
- iis安全設(shè)置全方位教程
- 巧妙出招致勝服務(wù)器管理
- Win Server 2003個(gè)人網(wǎng)絡(luò)服務(wù)器安全攻略
- Windows 2003校園Web服務(wù)器常見問題
- 清除IIS配置文件后門隱患
- Web服務(wù)器和應(yīng)用程序服務(wù)器有什么區(qū)別
- 虛擬主機(jī)下asp.net 2.0的導(dǎo)航控件treeview,menu等出錯(cuò)
- IIS6.0服務(wù)器架站無法訪問解決方案總結(jié)
- 圖解支持多語言環(huán)境的IIS服務(wù)器配置
- IIS服務(wù)器排錯(cuò)指南及錯(cuò)誤代碼大全
- 相關(guān)鏈接:
- 教程說明:
Web服務(wù)器教程-More Effective C++:通過引用捕獲異常
。