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