C++異常沉思錄之基礎篇

 

C++異常沉思錄之基礎篇

 

異常基本概念

異常即程序在運行期間產生的非預期性錯誤,如內存配額不足、文件不存在、網絡不可用、SQL語句非法等。

異常分爲可恢復性異常和不可恢復性異常。異常是否可恢復,不同的應用場景將有不同的理解。例如,對於內存配合不足,在短暫的峯涌情況下是可以快速恢復的,屬於可恢復性異常,一但內存被釋放和回收,程序將回到常態;而在內存長時間佔用的情況下,則可以認爲屬於不可恢復性異常,此時應當終止程序的運行。

異常又可分爲已知異常和未知異常。已知異常是指程序編寫者事先考慮到的異常,對異常有清晰的定義和處理邏輯;未知異常是指程序編寫者事先未察覺到的異常,可以理解爲程序bug。

異常捕獲是一把雙刃劍,對異常進行顯示捕獲,尤其對於bug型的未知異常,需要找出產生異常的原因和代碼進行修復,即需要考慮異常的分析定位機制。因此,對於未知異常或者無法處理的異常,不應當使用catch(…)籠統的進行捕獲,當不需要關心異常或者無法處理異常的時候,就應該避免捕獲異常或者將未處理的異常再次拋出,以便交由bugreport統一對未處理異常進行捕獲、分析。

C++提供了強大的異常處理機制將異常產生和異常處理的代碼分離,即將程序的正常業務邏輯和錯誤處理邏輯進行分離,並在不改變函數原型的情況下,實現異常對象的“向上”傳遞。

 

函數的異常聲明

在函數的聲明後面使用throw關鍵字可對函數的異常產生進行限制。

不允許函數拋出任何異常

void f() throw(); 表示f函數不允許拋出任何異常;以VS2005編譯器爲例,若在f函數中拋出異常,編譯時會有如下提示:

warning C4297: 'f' : function assumed not to throw an exception but does

1>        __declspec(nothrow) or throw() was specified on the function

但異常還是能正常拋出和被捕獲。

 

允許函數拋出任意異常

void f() throw(…);表示允許f函數拋出任意異常。

 

允許函數拋出指定類型的異常

void f() throw(MyException1,MyException2);表示允許函數f拋出MyException1和MyException2兩種類型的異常。以vs2005爲例,在f中拋出其他類型的異常,編譯未見提示,且異常能正常拋出和被捕獲。

 

異常的拋出

使用關鍵字throw拋出異常,有如下兩種語法。

throw express;拋出express所代表的表達式的異常,異常類型爲表達式的值的類型,異常的值爲表達式的值。例如,

try{

throw 10;

}catch(int e){

}

throw 10;語句拋出int型異常,異常的值爲10。catch(int e)語句能捕獲int型異常,e的值就是異常表達式的值10。

 

throw;將異常繼續“向上傳遞”。例如,

try{

throw 10;

}catch(int e){

         throw;

}

throw;語句將throw 10;語句產生的int型異常繼續向上傳遞。

 

異常類型

任何類型都可以作爲異常類型,包括基本數據類型,如int、double、char等;還包括類類型,如std:string;還包括自定義異常類型,即從std::exception派生的子類。

 

異常對象的傳遞

異常對象的傳遞和函數參數的傳遞一樣,有值傳遞、指針傳遞和引用傳遞3種傳遞方式。

值傳遞會產生臨時對象拷貝,且不支持多態。例如,

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func3() throw(MyException){

     throw MyException("func3 raised an exception.");

     return;

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

          func3();

     }catch(std::exception e){

         printf(e.what());

     }

     return 0;

}

///<運行結果:UnKnown exceptions。

 

因爲值拷貝不支持動態聯編,因此e.what()訪問的是基類exception的what方法,輸出的就是Unknown exceptions。

 

指針傳遞可以實現多態,若拋出臨時對象的地址,容易出現懸掛指針錯誤;若在堆上分配異常對象,使用完後需要對其進行刪除,一來刪除對象的時機不確定,二來代碼可讀性、可維護性遭到破壞,違反了從哪裏來回哪裏去的內存分配釋放原則。指針傳遞的示例代碼如下所示,

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func3() throw(MyException){

     throw new MyException("func3 raised an exception.");

     return;

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

          func3();

     }catch(std::exception* e){

         printf(e->what());

         delete e;///<釋放異常對象

     }

     return 0;

}

///<運行結果:func3 raised an exception.。

 

值引用傳遞既能避免臨時對象的拷貝,又能支持多態,且沒有對象釋放問題。推薦採用值引用的方式傳遞異常對象。示例代碼如下,

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func3() throw(MyException){

     throw MyException("func3 raised an exception.");

     return;

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

          func3();

     }catch(std::exception& e){

         printf(e.what());

     }

     return 0;

}

///<運行結果:func3 raised an exception.。

 

 

catch的匹配規則

異常匹配採用自底向上的匹配順序進行匹配,即先在異常產生的函數中進行匹配,若該造成該異常的代碼沒有被try…catch…捕獲或者catch類型不匹配,則向上傳遞直到匹配到第一條catch結束匹配,或則未發現任何匹配的catch則產生未處理異常交由運行時環境處理。

對於基本數據類型的異常,採用嚴格類型匹配機制,不支持類型轉換。例如,

try{

throw float(1.6);

}catch(double e){

}

拋出float類型的異常,double類型的catch是匹配不成功的。

對於自定義類型,基類類型能夠匹配子類異常對象,子類類型不能匹配基本異常對象。例如,

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func1() throw(){

     throw MyException ("MyException");

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

         func1();

     } catch(std::exception& e){ ///<使用基類類型匹配子類對象,匹配成功

         printf(e.what());

     } catch(MyException& e){

         printf(e.what());

     }

     return 0;

}

//運行結果:MyException

 

class MyException:public std::exception{

public: 

     MyException(const char* msg){

         msg_ = msg;

     }

     const char* what(){

         return msg_.c_str();

     }

protected:

     std::string msg_;

};

void func1() throw(){

     throw std::exception("std::exception");

}

int _tmain(int argc, _TCHAR* argv[])

{

     try{

         func1();

     }catch(MyException& e){///<使用子類類型匹配基類對象,匹配失敗

         printf(e.what());

     }catch(std::exception& e){///<匹配成功

         printf(e.what());

     }

     return 0;

}

//運行結果:std::exception

 

異常調用棧unwind

擬另起文檔專門進行描述。

 

供應寶異常體系結構

供應寶異常類受限於sens名字空間,其部分類圖如下所示。

 

sens::exception

供應寶異常基類,派生自c++標準異常類std::exception,統一異常處理行爲,如輸出堆棧,輸出日誌等。

showstack()

輸出異常產生時的堆棧,便於分析和定位異常。關於如何進行異常堆棧跟蹤,將另起文檔專門進行描述。

log()

將異常輸出到日誌文件中。

exception()

構造函數,調用showstack和log函數輸出異常的調用堆棧和異常日誌。

 

sens::dbexception

數據庫異常類。

 

sens::fileexception

文件異常類

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