C++異常處理初級出門+中級進階

概述

   今天聽了項目組裏的C++高手講C++的異常,受益匪淺。果然,與高手一起才能學習到更多的東西。下面我就把這位高手介紹的C++異常處理分享給園子裏的博友們。

什麼是異常呢?

   在編程語言裏,按照出現錯誤的時機來區分,有編譯期錯誤和運行期錯誤之分。

編譯期錯誤大家肯定很熟悉了,當我們build一個程序時,console裏出現的那些error提示就是編譯期錯誤。這些錯誤是在編譯期就能被編譯器檢查出來。

運行期錯誤大家可能不太經常見,因爲自己寫的程序往往都是在“溫室”裏運行的,很少看到程序崩潰的情況。運行期錯誤的種類很多,舉個例子,當我們要在堆上申請內存的時候,內存空間不足,或者在創建文件的時候,磁盤空間不足,這些都是運行期錯誤。我們用一個名字——異常來稱呼這樣的運行期錯誤。

C++如何捕獲異常?

   C++有自己異常處理體系,它捕獲異常的語法與java和C#很相似,可以看下面的代碼:

複製代碼
 1 int main(){
 2 
 3     try{
 4         cout<<"This an easy exception example."<<endl;
 5         throw 1;
 6     }catch(int i){
 7         cout<<"Catched the exception: i = "<<i<<endl;
 8     }
 9 
10     return 0;
11 }
複製代碼

語法很簡單,用try block來包含要捕獲異常區域的代碼,這段代碼裏會有throw語句拋出異常,再用catch來捕獲異常,上面代碼的輸出結果如下:

This an easy exception example.
Catched the exception: i = 1
請按任意鍵繼續. . .

慎用catch(...)

   C++的異常捕獲提供了一個萬能捕獲器,就是catch(...),它可以捕獲任意的異常,可以看出來,因爲沒有參數名,這樣我們就沒辦法獲取異常傳遞過來的內容了。不過還有一個重要的問題,就是catch(...)的位置問題。下面看一段代碼:

複製代碼
1     try{
2         cout<<"This an easy exception example."<<endl;
3         throw 1;
4     }catch(...){
5         cout<<"Catched the exception."<<endl;
6     }catch(int i){
7         cout<<"Catched the exception: i = "<<i<<endl;
8     }
複製代碼

上面的代碼把catch(...)放到了catch(int i)之前,這樣有什麼問題呢?catch(int i)包含的異常處理塊永遠不會被執行。強悍一點的編譯器會爲我們指出錯誤,但是有些編譯器就沒那麼強大了。所以,記住一條準則:catch(...)永遠放到所有捕獲catch處理的最後一個。

慎用繼承體系裏的類作爲catch的參數

   這個問題跟上面的問題類似,也是catch的優先級問題,看下面的代碼:

複製代碼
1     try{
2         DerivedClass dc;
3         cout<<"This an easy exception example."<<endl;
4         throw dc;
5     }catch(SuperClass s){
6         cout<<"Catched the exception:SuperClass."<<endl;
7     }catch(DerivedClass d){
8         cout<<"Catched the exception:DerivedClass."<<endl;
9     }
複製代碼

上面的代碼中,我們拋出了DerivedClass類的對象,本以爲會進入catch(DerivedClass d)的處理塊的,但是事實上它之調用了catch(SuperClass s)的處理塊。這個代碼編譯器不會去檢查,只能靠我們自己把握了,記住一個準則:要把最高級別的父類放到最後一個Catch裏處理。

對象析構函數被調用的三種場合?

   對象析構函數什麼時候會被調用呢?這裏先說一下,有三種情況析構函數被調用。哪三種呢?先看我們熟悉的兩種:

1 void func(){
2     A a;
3 }

第一種:上面的函數在調用時,函數完成調用後,會自動調用A的對象a的析構函數。

1     A *a = new A();
2     delete a;

第二種:顯示的調用delete語句也會調用對象的析構函數。

那第三種是什麼呢?其實你已經看到了,就是異常處理區域的throw語句,看下面的代碼:

1     try{
2         DerivedClass dc;
3         cout<<"This an easy exception example."<<endl;
4         throw dc;
5     }

上面的代碼,throw語句實際上爲我們調用了一次析構函數,儘管這個函數後面可能還有語句。實際上在拋出一個對象的時候,異常體系已經複製了一個tem_dc的對象。然後再調用DerivedClass的析構函數。所以,下面的代碼讓我們感到很惱怒:

1     try{
2         DerivedClass *pDc = new DerivedClass();
3         throw pDc;
4     }

我們希望把pDc指向的對象throw出來,實際上我們只是拋出了pDc這個指針,而這個對象早已經被析構掉了。所以這裏記住一個準則:儘量不要拋出指針和引用。

不要在異常處理體系中寄希望與類型轉換

  不要期望異常處理體系爲我們完成類型轉換,看下面的代碼:

1     try{
2         throw 'a';
3     }catch(int ch){
4         cout<<"this is a ch"<<endl;
5     }

平時寫代碼的時候,'a'是可以轉換成asic碼值的,但是這個時候就不行了,程序運行期是錯誤的。所以,記住一個準則:不要寄希望於C++異常處理體系會幫你做類型轉換。

有C++異常處理體系捕獲不到的東西嗎?

   有的,但也沒有,什麼意思呢。本來有的,後來被解決了。不知道你還記不記得住C++的成員初始化列表,不記得的話,咱們看下面的代碼:

複製代碼
 1 class Test{
 2 private:
 3     int age;
 4 public:
 5     Test():age(initialze(1)){
 6     
 7     }
 8     int initialze(int i){
 9         if(i == 1){
10             throw 1;
11         }else{
12             return 1;
13         }
14     }
15 };
複製代碼

上面的代碼中,構造函數在成員初始化列表中調用了可能拋出異常的initialize函數,這樣的異常怎麼捕獲呢?用前輩的一句話:C++於是提供了一種很醜的語法來解決這個問題。怎麼解決的呢?看下面的代碼:

複製代碼
1     Test()
2     try:age(initialze(1)){
3         {
4             //函數體
5         }
6     }catch(int i){
7         cout<<"exception"<<endl;
8     }
複製代碼

是不是很醜陋的語法,我也覺得怎麼好看,不過確實捕獲了成員初始化列表的異常。

set_unexpected函數的用處

   這個函數的作用,簡而言之,就是設置默認異常處理函數。什麼意思呢?看下面的代碼就瞭解了。

複製代碼
 1 void myFunc(){
 2     cout<<"set_unexpected Exception."<<endl;
 3     throw 0;
 4 }
 5 void fun(int x) throw(char)
 6 {
 7     throw 'a';
 8 }
 9 int main(){
10     set_unexpected(myFunc);
11     try{
12         fun(1);
13     }catch(int i){
14         cout<<"int exception"<<endl;
15     }
16 
17     system("pause");
18     return 0;
19 }
複製代碼

從main開始看,我們註冊了一個默認異常處理函數,這個函數會對異常做一個修正。fun函數裏拋出char的異常,我們的語句是捕獲不了的,所以經過默認處理函數修正之後,就可以用catch(int i)捕獲了。

不過,上面的代碼在VS上運行是不行的,linux下運行就okay了。

Effective C++:不要讓異常逃離析構函數

    看懂了上面的,你就已經很厲害啦,當然這只是個玩笑。EffiectiveC++裏有一條:不要讓異常逃離析構函數。什麼意思呢?就是當我們遇到下面這樣的代碼後:

複製代碼

1
try{ 2 DerivedClass dc; 3 cout<<"This an easy exception example."<<endl; 4 throw dc; 5 }catch(SuperClass s){ 6 cout<<"Catched the exception:SuperClass."<<endl; 7 }catch(DerivedClass d){ 8 cout<<"Catched the exception:DerivedClass."<<endl; 9 }
複製代碼

上面的代碼throw dc之後,會調用DerivedClass的析構函數,這樣的話,如果析構函數再拋出異常,我們的捕獲函數就悲劇的不知道該如何處理了。也就是說,當同時出現兩個throw拋出的異常之後,程序就會直接宕掉。所以,不要讓異常逃離析構函數,否則,你就悲劇了。


轉自:http://www.cnblogs.com/alephsoul-alephsoul/archive/2012/11/30/2796905.html

發佈了15 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章