C++編程技術之 異常處理(下)

標準異常:

使用標準庫定義的異常要比先前的自己創建異常類方便得多,當然,也可以從標準異常類中派生出自己的類。

所有的標準異常都是從exception類派生的,該類定義在<exception>中,主要的兩個派生類爲logic_error和runtime_error,這兩個類定義在<stdexcept>中(這個頭文件包含exception)。logic_error描述程序中出現的邏輯錯誤,如傳遞無效的參數。runtime_error描述無法預料的事造成的錯誤,無硬件錯誤或內存耗盡。這兩類錯誤提供了std::string的構造參數,可以將信息保存在異常對象中,通過exception::what( )函數,可以從對象中得到保存的信息:

#include <stdexcept>
#include <iostream>
using namespace std;

class MyError: public runtime_error{
public:
    MyError(const string& msg=""):runtime_error(msg){}
};

int main()
{
    try{
        throw MyError("my message");
    }catch(MyError& x){
        cout<<x.what()<<endl;
    }
}

儘管runtime_error的構造函數把消息保存在std::exception在對象中,但std::exception並沒有提供一個參數類型爲std::string的構造函數,所以還是最好是派生自己的異常類。

輸入輸出流異常ios::failure也是由exception派生的,但它沒有子類。

異常規格說明

有時不要求程序提供資料告訴函數的使用者在函數使用時會拋出什麼異常,但這樣的話,函數的使用者無法確定如何編碼捕獲可能出現的異常。C++提供一種語法告訴函數的使用者所拋出的異常,稱爲異常規格說明,它是函數聲明的修飾符,放在參數列表後面。

函數可能拋出的異常在throw後面的括號中

void f() throw(toobig,toosmall,divzero);

不添加該修飾符則表明可能拋出任何異常。

void f() throw()表示不會拋出任何異常

1.unexcepted()函數

如果函數拋出的異常沒有在異常規格說明中,那麼unexcepted()函數就會被調用,默認該函數會調用之前的terminate()函數。

2.set_unexcepted()函數

可以設置自己的unexcepted()函數,例如:

#include <exception>
#include <iostream>
using namespace std;

class Up{};
class Fit{};
void g();

void f(int i) throw(Up,Fit){
    switch(i){
        case 1:throw Up();
        case 2:throw Fit();
    }
    g();
}

//void g(){}        //version1
void g(){throw 47;} //version2

void my_unexcepter(){
    cout<<"unexcepted exception thrown"<<endl;
}

int main()
{
    set_unexpected(my_unexcepter);
    for(int i=1;i<=3;i++)
    {
        try{
            f(i);
        }catch(Up){
            cout<<"up caught"<<endl;
        }catch(Fit){
            cout<<"Fit caught"<<endl;
        }
    }
}

典型的unexcepted處理器會將錯誤記錄日誌,然後調用exit終止程序。它也可以拋出另外一個異常或調用abort()。如果它拋出的異常類型不在違反觸發unexcepted的函數的異常規格說明,那麼程序將會恢復到被調用的位置重新開始異常匹配。

如果仍舊不符合異常規範,下面兩種情況之一將會發生:

1.如果函數的異常規格說明中包括std::bad_exception,那麼拋出的異常將會被換成這個對象,然後重新匹配

2.如果不包含std::bad_exception,那麼直接調用terminate()函數。

#include <exception>
#include <iostream>
#include <cstdio>
#include <stdlib.h>
using namespace std;

class A{};
class B{};

void my_thandler(){
    cout<<"terminate called"<<endl;
    exit(0);
}

void my_unhandler1(){throw A();}
void my_unhandler2(){throw;}

void t(){throw B();}

void f() throw(A) {t();}
void g() throw(A,bad_exception){t();}

int main()
{
    set_terminate(my_thandler);
    set_unexpected(my_unhandler1);
    try{
        f();
    }catch(A&){
        cout<<"cathc an A from t"<<endl;
    }
    set_unexpected(my_unhandler2);
    try{
        g();
    }catch(bad_exception&){
        cout<<"catch a bad_exception from g"<<endl;
    }
    try{
        f();
    }catch(...){
        cout<<"this will never be print"<<endl;
    }
}

異常規格說明和繼承

先看一個例子:

#include <iostream>
using namespace std;

class Base{
public:
    class BaseException{};
    class DerivedException: public BaseException{};
    virtual void f() throw(DerivedException){
        throw BaseException();
    }
};

class Derived:public Base{
public:
    void f() throw(BaseException){
        throw BaseException();
    }
    virtual void g() throw(DerivedException){
        throw DerivedException();
    }
};

由於Derived::f()違反了Base::f()的規定,所以編譯會報錯。

異常安全

在棧容器中,pop()的成員函數聲明如下:

void pop();

該函數僅僅只是刪除了棧頂的元素,而爲了獲得棧頂元素,需要在pop()在前調用top()。這麼做的很重要的原因就是棧必須保證異常安全。

假設使用動態數組實現一個棧(data數組名),pop具有返回值:

template<class T>T stack<T>::pop(){

if(count==0)

throw logic_error("stack underflow");

else

return data[--count];

}

如果爲了得到返回值而調用拷貝構造函數,函數卻在最後一行拋出一個異常,函數沒有將應該退棧的元素返回,但是count的值已經減1,所以,要遵守內聚設計原則,每個函數只做一件事

什麼時候避免使用異常:

1、不要在異步事件中使用

2、不要在處理簡單錯誤的時候使用異常

3、不要將異常用於程序的流程控制

4、不要強迫自己使用異常

5、新異常,老代碼





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