C++異常機制

C++異常機制

 

一、 概述

C++自身有着非常強的糾錯能力,發展到如今,已經建立了比較完善的異常處理機制。C++的異常情況無非兩種,一種是語法錯誤,即程序中出現了錯誤的語句,函數,結構和類,致使編譯程序無法進行。另一種是運行時發生的錯誤,一般與算法有關。

關於語法錯誤,不必多說,寫代碼時心細一點就可以解決。C++編譯器的報錯機制可以讓我們輕鬆地解決這些錯誤。

第二種是運行時的錯誤,常見的有文件打開失敗、數組下標溢出、系統內存不足等等。而一旦出現這些問題,引發算法失效、程序運行時無故停止等故障也是常有的。這就要求我們在設計軟件算法時要全面。比如針對文件打開失敗的情況,保護的方法有很多種,最簡單的就是使用“return”命令,告訴上層調用者函數執行失敗;另外一種處理策略就是利用c++的異常機制,拋出異常。
    
二、c++異常處理機制

    C++異常處理機制是一個用來有效地處理運行錯誤的非常強大且靈活的工具,它提供了更多的彈性、安全性和穩固性,克服了傳統方法所帶來的問題.
    
    異常的拋出和處理主要使用了以下三個關鍵字: try、 throw 、 catch 。
   
    拋出異常即檢測是否產生異常,在C++中,其採用throw語句來實現,如果檢測到產生異常,則拋出異常。該語句的格式爲:
    throw 表達式;
    如果在try語句塊的程序段中(包括在其中調用的函數)發現了異常,且拋棄了該異常,則這個異常就可以被try語句塊後的某個catch語句所捕獲並處理,捕獲和處理的條件是被拋棄的異常的類型與catch語句的異常類型相匹配。由於C++使用數據類型來區分不同的異常,因此在判斷異常時,throw語句中的表達式的值就沒有實際意義,而表達式的類型就特別重要。
 
try-catch語句形式如下 :

  1. try 

  2. {  

  3.         包含可能拋出異常的語句;  

  4. }  

  5. catch(類型名 [形參名]) // 捕獲特定類型的異常  

  6. {  

  7.  

  8. }  

  9. catch(類型名 [形參名]) // 捕獲特定類型的異常  

  10. {  

  11.  

  12. }  

  13. catch(...)    // 三個點則表示捕獲所有類型的異常  

  14. {  


【範例1】處理除數爲0的異常。該範例將上述除數爲0的異常可以用try/catch語句來捕獲異常,並使用throw語句來拋出異常,從而實現異常處理,實現代碼如代碼清單1-1所示。
// 代碼清單1-1

  1. #include<iostream.h>     //包含頭文件  

  2. #include<stdlib.h>  

  3.  

  4. double fuc(double x, double y) //定義函數  

  5. {  

  6.     if(y==0)  

  7.     {  

  8.         throw y;     //除數爲0,拋出異常  

  9.     }  

  10.     return x/y;     //否則返回兩個數的商  

  11. }  

  12.  

  13. void main()  

  14. {  

  15.     double res;  

  16.     try  //定義異常  

  17.     {  

  18.         res=fuc(2,3);  

  19.         cout<<"The result of x/y is : "<<res<<endl;  

  20.         res=fuc(4,0); 出現異常,函數內部會拋出異常  

  21.     }  

  22.     catch(double)             //捕獲並處理異常  

  23.     {  

  24.          cerr<<"error of dividing zero.\n";  

  25.          exit(1);                //異常退出程序  

  26.     }  

【範例2】自定義異常類型 

// 代碼清單1-2

  1. #include "stdafx.h"  

  2. #include<stdlib.h>  

  3. #include<crtdbg.h>  

  4. #include <iostream>  

  5. // 內存泄露檢測機制  

  6. #define _CRTDBG_MAP_ALLOC   

  7. #ifdef _DEBUG  

  8. #define new new(_NORMAL_BLOCK, __FILE__, __LINE__)  

  9. #endif  

  10.  

  11. // 自定義異常類  

  12. class MyExcepction  

  13. {  

  14. public:  

  15.  

  16.         // 構造函數,參數爲錯誤代碼  

  17.         MyExcepction(int errorId)  

  18.         {  

  19.             // 輸出構造函數被調用信息  

  20.             std::cout << "MyExcepction is called" << std::endl;  

  21.             m_errorId = errorId;  

  22.         }  

  23.  

  24.         // 拷貝構造函數  

  25.         MyExcepction( MyExcepction& myExp)  

  26.         {  

  27.             // 輸出拷貝構造函數被調用信息  

  28.             std::cout << "copy construct is called" << std::endl;  

  29.             this->m_errorId = myExp.m_errorId;  

  30.         }  

  31.  

  32.        ~MyExcepction()  

  33.         {  

  34.             // 輸出析構函數被調用信息  

  35.             std::cout << "~MyExcepction is called" << std::endl;  

  36.         }  

  37.  

  38.        // 獲取錯誤碼  

  39.         int getErrorId()  

  40.         {  

  41.             return m_errorId;  

  42.         }  

  43.  

  44. private:      

  45.         // 錯誤碼  

  46.         int m_errorId;  

  47. };  

  48.  

  49. int main(int argc, char* argv[])  

  50. {  

  51.         // 內存泄露檢測機制  

  52.         _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );  

  53.  

  54.         // 可以改變錯誤碼,以便拋出不同的異常進行測試  

  55.         int throwErrorCode = 110;  

  56.  

  57.        std::cout << " input test code :" << std::endl;  

  58.        std::cin >> throwErrorCode;  

  59.  

  60.        try 

  61.        {  

  62.             if ( throwErrorCode == 110)  

  63.             {  

  64.                 MyExcepction myStru(110);  

  65.  

  66.                 // 拋出對象的地址 -> 由catch( MyExcepction*    pMyExcepction) 捕獲  

  67.                 // 這裏該對象的地址拋出給catch語句,不會調用對象的拷貝構造函數  

  68.                 // 傳地址是提倡的做法,不會頻繁地調用該對象的構造函數或拷貝構造函數  

  69.                 // catch語句執行結束後,myStru會被析構掉  

  70.                 throw    &myStru;      

  71.             }  

  72.             else if ( throwErrorCode == 119 )  

  73.             {  

  74.                 MyExcepction myStru(119);  

  75.  

  76.                 // 拋出對象,這裏會通過拷貝構造函數創建一個臨時的對象傳出給catch  

  77.                 // 由catch( MyExcepction    myExcepction) 捕獲  

  78.                 // 在catch語句中會再次調用通過拷貝構造函數創建臨時對象複製這裏傳過去的對象  

  79.                 // throw結束後myStru會被析構掉  

  80.                 throw    myStru;      

  81.              }  

  82.              else if ( throwErrorCode == 120 )  

  83.              {  

  84.                   // 不提倡這樣的拋出方法  

  85.                   // 這樣做的話,如果catch( MyExcepction*    pMyExcepction)中不執行delete操作則會發生內存泄露  

  86.  

  87.                   // 由catch( MyExcepction*    pMyExcepction) 捕獲  

  88.                   MyExcepction * pMyStru = new MyExcepction(120);   

  89.                   throw pMyStru;      

  90.              }  

  91.              else 

  92.              {  

  93.                   // 直接創建新對象拋出  

  94.                   // 相當於創建了臨時的對象傳遞給了catch語句  

  95.                   // 由catch接收時通過拷貝構造函數再次創建臨時對象接收傳遞過去的對象  

  96.                   // throw結束後兩次創建的臨時對象會被析構掉  

  97.                    throw MyExcepction(throwErrorCode);      

  98.              }      

  99.         }  

  100.         catch( MyExcepction*    pMyExcepction)  

  101.         {  

  102.              // 輸出本語句被執行信息  

  103.                std::cout << "執行了 catch( MyExcepction*    pMyExcepction) " << std::endl;  

  104.  

  105.              // 輸出錯誤信息  

  106.                std::cout << "error Code : " << pMyExcepction->getErrorId()<< std::endl;  

  107.  

  108.             // 異常拋出的新對象並非創建在函數棧上,而是創建在專用的異常棧上,不需要進行delete  

  109.             //delete pMyExcepction;  

  110.         }  

  111.         catch ( MyExcepction myExcepction)  

  112.         {  

  113.             // 輸出本語句被執行信息  

  114.             std::cout << "執行了 catch ( MyExcepction myExcepction) " << std::endl;  

  115.  

  116.             // 輸出錯誤信息  

  117.             std::cout << "error Code : " << myExcepction.getErrorId()<< std::endl;  

  118.         }  

  119.         catch(...)  

  120.         {  

  121.              // 輸出本語句被執行信息  

  122.              std::cout << "執行了 catch(...) " << std::endl;  

  123.  

  124.              // 處理不了,重新拋出給上級  

  125.              throw ;  

  126.         }  

  127.  

  128.         // 暫停  

  129.         int temp;  

  130.         std::cin >> temp;  

  131.  

  132.        return 0;  

  133.  


三、異常的接口聲明

爲了加強程序的可讀性,使函數的用戶能夠方便地知道所使用的函數會拋出哪些異常,可以在函數的聲明中列出這個函數可能拋出的所有異常類型,例如:

void fun() throw( A,B,C,D);

這表明函數fun()可能並且只可能拋出類型(A,B,C,D)及其子類型的異常。

如果在函數的聲明中沒有包括異常的接口聲明,則此函數可以拋出任何類型的異常,例如:

void fun();
 


一個不會拋出任何類型異常的函數可以進行如下形式的聲明:
 

void fun() thow();

      
五、異常處理中需要注意的問題

1. 如果拋出的異常一直沒有函數捕獲(catch),則會一直上傳到c++運行系統那裏,導致整個程序的終止

2. 一般在異常拋出後資源可以正常被釋放,但注意如果在類的構造函數中拋出異常,系統是不會調用它的析構函數的,處理方法是:如果在構造函數中要拋出異常,則在拋出前要記得刪除申請的資源。

3. 異常處理僅僅通過類型而不是通過值來匹配的,所以catch塊的參數可以沒有參數名稱,只需要參數類型。

4. 函數原型中的異常說明要與實現中的異常說明一致,否則容易引起異常衝突。
 
5. 應該在throw語句後寫上異常對象時,throw先通過Copy構造函數構造一個新對象,再把該新對象傳遞給 catch. 
       那麼當異常拋出後新對象如何釋放?
       異常處理機制保證:異常拋出的新對象並非創建在函數棧上,而是創建在專用的異常棧上,因此它纔可以跨接多個函數而傳遞到上層,否則在棧清空的過程中就會被銷燬。所有從try到throw語句之間構造起來的對象的析構函數將被自動調用。但如果一直上溯到main函數後還沒有找到匹配的catch塊,那麼系統調用terminate()終止整個程序,這種情況下不能保證所有局部對象會被正確地銷燬。
   
6. catch塊的參數推薦採用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用對象的多態性。另外,派生類的異常撲獲要放到父類異常撲獲的前面,否則,派生類的異常無法被撲獲。
   
7. 編寫異常說明時,要確保派生類成員函數的異常說明和基類成員函數的異常說明一致,即派生類改寫的虛函數的異常說明至少要和對應的基類虛函數的異常說明相同,甚至更加嚴格,更特殊。

出處: http://www.cnblogs.com/armstrong-cn/archive/2011/09/02/2163553.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章