1. 程式人生 > >boost庫實用工具之exception

boost庫實用工具之exception

    異常是C++處理錯誤的重要機制,他改變了傳統的使用錯誤返回值的處理模式,簡化了函式的介面和呼叫程式碼,有助於編寫整潔,優雅,健壯的程式。C++標準庫中定義的標準異常類std::exception及其一些子類是整個C++語言處理的基礎。
    而boost.exception 庫針對標準庫中異常類的缺陷進行了強化,提供了<<操作符過載,可以向異常傳入任意資料,有助於增加異常的資訊和表達力,其中部分特性已經加入C++11標準。
    exception位於名字空間boost中,為了使用exception,需要包含標頭檔案boost/exception/all.hpp

標準庫中的列表

為了使用boost.exception,我們需要先了解c++標準規定的異常體系。
c++標準中定義了一個異常基類std::exception和try/catch/throw異常處理機制,std::exception又派生出若干子類,用以描述不同種類的異常,如bad_alloc, bad_cast, out_or_range等,共同構建了C++異常處理框架。
C++允許任何型別作為異常丟擲,但在std::exception出現後,我們應該儘量使用它,因為std::exception提供了一個很有用的函式what(),可以返回異常所攜帶的資訊。
有時我們需要定義自己的錯誤資訊,我們需要繼承std::exception及其子類。例如:

class my_exception : public std::logic_error
{
private:
    int err_no;//錯誤資訊碼
public:
    my_exception(const char* msg, int err):
        std::logic_error(msg)
        , err_no(err)
    {
        //
    }
    int get_err_no() { return err_no; }
};

如果系統需要很多不同種類的異常時,這種方法就會成為負擔——需要編譯異常處理大量程式碼。
而且這種解決法還存在一個問題:很多時候當異常發生時不能獲得有異常的完全診斷資訊,而標準庫的異常類一旦出現,它就成了一個“死”物件,程式失去了對它的控制能力,只能使用它或者再丟擲一個新的異常。
boost.exception針對這些異常定義了新的異常類boost.exception完善了c++的異常處理機制。

類摘要

exception庫提供了兩個類:exception和error_info,他們是exception庫的基礎。

exception

class exception
{
public:
    exception();
    exception(exception const& x);
    virtual ~exception();

private:
    template<class E,class Tag,class T>
    friend E const& operator<<(E const&, error_info<Tag, T> const&);
};
typename value_type* get_error_info(E&x);

注意:它是一個抽象類,幾乎沒有公開的成員函式。除了它的子類,任何人都不能建立或者銷燬它,這保證了exception不被誤用。
exception的重要能力在於其友元操作符<<,可以儲存error_info物件的資訊,存入的資訊可以用自由函式get_error_info<>()隨時取出來。這個函式返回一個儲存資料的指標,如果exception裡米有這一種型別的資訊返回空。
exception特意沒有從std::exception繼承。

error_info

class exception
{
public:
    exception();
    exception(exception const& x);
    virtual ~exception();

private:
    template<class E,class Tag,class T>
    friend E const& operator<<(E const&, error_info<Tag, T> const&);
};
typename value_type* get_error_info(E& x);
error_info 提供了向異常型別新增資訊的通用解法。第一個模板型別引數Tag是一個標記,他通常(最好)是一個空類,僅用來標記error_info類,使他在模板例項化時生成不同的型別。第二個引數T是真正儲存的資訊資料,可以用成員函式value()訪問

向異常傳遞資訊

exception和error_info被設計為配合std::exception一起工作,自定義的異常類可以安全地從exception和std::exception多重繼承,從而獲得兩者的能力。
注意exception是抽象的,我們必須自己定義它的子類才能使用它,如前所述,exception必須使用虛繼承的方式。通常,繼承完成後自定義異常類的實現也就完成了,不需要“畫蛇添足”地向它增加成員函式或者成員變數。這些都已經由exception完成了。例如:

struct my_excpetion:
    virtual std::exception,//虛繼承,struct 預設public繼承
    virtual boost::exception // 虛繼承,struct 預設public繼承
{
    //空實現,不需要實現程式碼。
};

接下來我們需要定義我們要儲存的資訊–使用模板類,error_info。用一個struct作為第一個模板引數來標記資訊型別,再用第二個模板引數指定資訊的資料型別。由於error_info<>的型別定義較長,我們需要用到typedef

例如下面的程式碼用error_info定義兩個儲存int和string的資訊類:

typedef boost::error_info<struct tag_err_no, int> err_no;
typedef boost::error_info<struct tag_err_str, string> err_str;

當異常發生時,我們就可以建立一個自定義的異常類,並用<<操作符來向它儲存任意資訊,這些資訊可以在任何時候用get_error_info()函式獲取。
示範程式碼如下:

struct my_excpetion:
    virtual std::exception,//虛繼承,struct 預設public繼承
    virtual boost::exception // 虛繼承,struct 預設public繼承
{
    //空實現,不需要實現程式碼。
};
typedef boost::error_info<struct tag_err_no, int> err_no;
typedef boost::error_info<struct tag_err_str, string> err_str;

void case1()
{
    try
    {
        try {
            // 丟擲異常,儲存資訊碼
            throw my_excpetion() << err_no(10);
        }
        catch (my_excpetion& e)
        {
            //獲取異常記憶體儲的資訊
            cout << *get_error_info<err_no>(e) << endl;
            cout << e.what() << endl;
            e << err_str("other info"); //向異常內追加資訊
            throw; //然後再次丟擲異常
        }
    }
    catch (my_excpetion& e) // function_try 的捕獲程式碼
    {
        // 獲得異常資訊
        cout << *get_error_info<err_str>(e) << endl;
    }

}

錯誤資訊類

處理異常的一個工作是定義錯誤型別,我們用typedef來具體化error_info模板類。這通常比較麻煩,exception庫特意預定義了若干個定義好的錯誤資訊類

typedef error_info<...> errinfo_api_function;
typedef error_info<...> errinfo_at_line;
typedef error_info<...> errinfo_errno;
typedef error_info<...> errinfo_file_handle;
typedef error_info<...> errinfo_file_open_mode;
typedef error_info<...> errinfo_type_info_name;

它們可以用於常見的呼叫API,行號,錯誤程式碼,檔案handle,檔名等錯誤資訊的處理。例如

try {
    throw my_excpetion() << errinfo_api_function("call api") << errinfo_errno(10);
}
catch (boost::exception& e)
{
    cout << *get_error_info<errinfo_api_function>(e);
    cout << *get_error_info<errinfo_errno>(e);
}

另外,exception庫還提供了三個預定義的錯誤資訊型別,但命名規則略有不同:
typedef error_info<…> throw_function;
typedef error_info<…> throw_file;
typedef error_info<…> throw_line;
這三個錯誤資訊類,主要用於儲存原始碼的資訊,配合巨集BOOST_CURRENT_FUNCTIONFILELINE使用,可以獲取函式名,原始檔名和原始碼行號。

如果這些預定義的還不能滿足要求,我們還需要用typedef來定義我們錯誤資訊。
可以用巨集DEFINE_ERROR_INFO。

#define DEFINE_ERROR_INFO(type, name) \
    typedef boost::error_info<struct tag_##name, type> name

DEFINE_ERROR_INFO<int,err_no>; 就相當於
typedef boost::error_info<struct tag_err_no,int >err_no

包裝標準異常

exception庫提供了一個模板函式enable_error_info(T& e), 其中T是標準異常類或者其他自定義型別。它可以包裝型別T,產生從boost::exception和T派生的類,從而不修改原異常處理體系的前提下獲得boost::exception的所有好處。如果型別T已經是boost::exception的子類,那麼enable_error_info將返回e的一個拷貝。
enable_error_info()通常在程式中已經存在異常類的場合,對這些異常類的修改很困難甚至不可能(比如已經編譯成庫)。這時候enable_error_info()就可以包裝原有的異常類,從而很容易的在不改變原有程式碼的基礎上把boost::exception整合到原有的異常體系中去。

//自定義的一個異常類,未使用boost::exception
struct my_err{};

void cast2()
{
    try {
        //使用enable_error_info包裝自定義的異常
        throw enable_error_info(my_err()) << errinfo_errno(10);
        throw enable_error_info(std::runtime_error("runtime"))
        << errinfo_at_line(__LINE__);
    }
    catch (boost::exception& e)//這裡必須使用errinfo_errno(10)來捕獲
    {
        cout << *get_error_info<errinfo_errno>(e) << endl;
    }
}

使用函式丟擲異常

exception庫在標頭檔案