C++異常處理

linlinlinxi007發表於2010-01-28

1.         異常處理的使用

首先說明,千萬別對異常處理鑽牛角尖,那樣會死人的(當然是煩死的)!

在C++程式設計處理中,我秉承這樣一個思想,就是:能不用異常處理的就不用。因為造成的混亂實在是太——多了。如果能用其他方法捕捉到錯誤並處理的話,誓死不用異常處理!呵呵,或許有點偏激,但我認為,這不失為一個避免不必要的錯誤的一個好辦法。當什麼分配記憶體失敗,開啟檔案失敗之類的通常錯誤,我們只需用assert,abort之類的函式就解決問題了。也就是說,假如有足夠的資訊去處理一個錯誤,那麼這個錯誤就不是異常。

        當然了,異常處理的存在也有它本身的意義和作用。不是你說不用就不用的,有些地方還非得用不可!

        比如說,在當前上下文環境中,無法捕捉或確定的錯誤型別,我們就得用一個異常丟擲到更大的上下文環境當中去。還有,異常處理的使用呢,可以使出錯處理程式與“通常”程式碼分離開來,使程式碼更簡潔更靈活。另外就是程式必不可少的健壯性了,異常處理往往在其中扮演著重要的角色。

        OK,下面闡述一下。

2.         丟擲異常

關——鍵字(周星馳的語氣):throw

例——句:throw ExceptionClass(“oh, shit! it’s a exception!L “);

例句中,ExceptionClass是一個類,它的建構函式以一個字串做為引數,用來說明異常。也就是說,在throw的時候,C++的編譯器先構造一個ExceptionClass的物件,讓它作為throw的返回值,拋——出去。同時,程式返回,呼叫析構。看下面這個程式:

#include <iostream.h>

 

class ExceptionClass{

        char* name;

public:

        ExceptionClass(char* name="default name")        {

               cout<<"Construct "<<name<<endl;

               this->name=name;

        }

        ~ExceptionClass()        {

               cout<<"Destruct "<<name<<endl;

        }

        void mythrow(){

        throw ExceptionClass("o,my god");

}       

};

 

void main(){

        ExceptionClass e("haha");

        try      {

               e.mythrow();

        }        catch(...)     {

        }

}

大家看看結果就知道了,throw後,呼叫當前類的析構,整個結束了這個類的歷史使命。唉~~

3.         異常規格說明

如果我們呼叫別人的函式,裡面有異常丟擲,我用去檢視它的原始碼去看看都有什麼異常丟擲嗎?可以,但是太——煩躁。比較好的解決辦法,是編寫帶有異常丟擲的函式時,採用異常規格說明,使我們看到函式宣告就知道有哪些異常出現。

異常規格說明大體上為以下格式:

void ExceptionFunction(argument…) throw(ExceptionClass1, ExceptionClass2, ….)

對了,所有異常類都在函式末尾的throw()的括號中得以說明了,這樣,對於函式呼叫者來說,是一清二楚了!

注意下面一種形式:

void ExceptionFunction(argument…) throw()

表明沒有任何異常丟擲。

而正常的void ExceptionFunction(argument…)則表示:可能丟擲任何一種異常,當然就,也可能沒有異常,意義是最廣泛的哦。

4.         構造和析構中的異常丟擲

55555,到了應該注意的地方了。

        先看個程式,假如我在建構函式的地方丟擲異常,這個類的析構會被呼叫嗎?可如果不呼叫,那類裡的東西豈不是不能被釋放了??

        程式:

#include <iostream.h>

#include <stdlib.h>

 

class ExceptionClass1{

        char* s;

public:

        ExceptionClass1(){

               cout<<"ExceptionClass1()"<<endl;

               s=new char[4];

               cout<<"throw a exception"<<endl;

               throw 18;

        }

        ~ExceptionClass1(){

               cout<<"~ExceptionClass1()"<<endl;

               delete[] s;

        }

};

 

void main(){

        try{

               ExceptionClass1 e;

        }catch(...)

        {}

}

結果為:

ExceptionClass1()

throw a exception

沒了,沒了,到此為止了!可是,可是,在這兩句輸出之間,我們已經給S分配了記憶體,哪裡去了?記憶體釋放了嗎?沒有,沒有,因為它是在解構函式中釋放的,哇!問題大了去了。怎麼辦?怎麼辦?

為了避免這種情況,應避免物件通過本身的建構函式涉及到異常丟擲。即:既不在建構函式中出現異常丟擲,也不應在建構函式呼叫的一切東西中出現異常丟擲。否則,只有完蛋。

那麼,在解構函式中的情況呢?我們已經知道,異常丟擲之後,就要呼叫本身的解構函式,如果這解構函式中還有異常丟擲的話,則已存在的異常尚未被捕獲,會導致異常捕捉不到哩。

完,也就是說,我們不要在建構函式和解構函式中存在異常丟擲。

5.         異常捕獲

上邊的程式不知道大家看懂了沒,異常捕獲已經在上面出現了也。

沒錯,就是try{…}catch(…){…}這樣的結構!

Try後面的花括號中,就是有可能涉及到異常的各種宣告啊呼叫啊之類的,如果有異常丟擲,就會被異常處理器截獲捕捉到,轉給catch處理。先把異常的類和catch後面小括號中的類進行比較,如果一致,就轉到後面的花括號中進行處理。

例如丟擲異常是這麼寫的:

void f(){throw ExceptionClass(“ya, J”);}

假設類ExceptionClass有個成員函式function()在有異常時進行處理或相應的訊息顯示(只是做個例子哦,別挑我的刺兒)。

那麼,我可以這麼捕捉: try{f()}catch(ExceptionClass e){e.function()};

當然,象在上面程式中出現的一樣,我可以在catch後用三個點來代表所有異常。如try{f()}catch(…){}。這樣就截斷了所有出現的異常。有助於把所有沒出現處理的異常遮蔽掉(我是這麼認為的J)。

異常捕獲之後,我可以再次丟擲,就用一個不帶任何引數的throw語句就可以了,例如:try(f())catch(…){throw}

 

6.         標準異常

正象許多人想象的一樣,C++肯定有自己的標準的異常類。

一個總基類:

exception               是所有C++異常的基類。

下面派生了兩個異常類:

logic_erro               報告程式的邏輯錯誤,可在程式執行前被檢測到。

runtime_erro          顧名思義,報告程式執行時的錯誤,只有在執行的時候才能檢測到。

以上兩個又分別有自己的派生類:

由logic_erro派生的異常類

domain_error                 報告違反了前置條件

invalid_argument            指出函式的一個無效引數

length_error 指出有一個產生超過NPOS長度的物件的企圖(NPOS為size_t的最大可表現值

out_of_range 報告引數越界

bad_cast                       在執行時型別識別中有一個無效的dynamic_cast表示式

bad_typeid 報告在表示式typeid(*p)中有一個空指標P

 

由runtime_error派生的異常

range_error 報告違反了後置條件

overflow_error 報告一個算術溢位

bad_alloc                      報告一個儲存分配錯誤