C++基礎::自制異常定位器

Inside_Zhang發表於2015-11-10

我們經常會有這樣的需求,異常或錯誤(又或者記憶體洩露時)發生時,如何進行快速定位,定位到檔案一級、定位到函式一級、乃至定位到異常出現的行號一級。如此高大上的需求,只需要瞭解C++ preprocessor內建的一些巨集定義如__FILE__(檔名),__LINE__(行號),以及boost\current_function.hpp 中的BOOST_CURRENT_FUNCTION(函式名),將這些巨集定義以引數的形式傳遞給一個異常類,再施以一定的封裝,便可輕鬆實現對異常出現位置的捕捉。

#include <exception>
#include <boost\shared_ptr.hpp>
#include <sstream>

using namespace std;

class Error :public exception
{
public:
    Error(const string& file, long line, 
                const string& func, const string& msg);
    const char* what() const;           // 重寫父類的what函式
private:
    string format(const string& file, long line,
                const string& func, const string& msg);
    boost::shared_ptr<string> _msg;   
                    // 操作_msg, 如同操作一個string* 
};

Error::Error(const string& file, long line, 
            const string& func, const string& msg)
{
    _msg = boost::shared_ptr<string>(new string(
            format(file, line, func, msg)));
}

string format(const string& file, long line, 
            const string& func, const string& msg)
{
    ostringstream oss;          // #include <sstream>
    oss << func << ":\n";
    oss << file << "(" << line << ").\n" << msg;
    return oss.str();
}

const char* Error::what() const
{
    return _msg.c_str();
}

客戶端程式:

double divide(double x, double y)
{
    if (y == 0.)
        throw Error(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, "除數不能為0");    // #include <boost\current_function.hpp>
    return x/y;
}
int main(int, char**)
{
    try
    {
        divide(1., 0.);
    }
    catch(exception& e)
    {
        cout << e.what() << endl;
    }
}

C++前處理器也提供瞭如下的巨集定義:

__DATE__
__TIME__

當然一種更常規的做法,利用巨集定義(也只能用巨集,而不可使用inline 行內函數取代,不由分說的原樣替換雖然臭名昭著,卻也有時非它不可)的原樣替換的特性,對此做進一步的封裝,避免顯式傳參的動作:

#define FAIL(msg)\
    std::ostringstream oss; \
    oss << msg; \
    throw Error(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, 
            oss.str());

#define ASSERT(predicate, msg)\
    if (!(predicate)) \
    {    \
        FAIL(msg);\
    }

這樣客戶端程式碼就可改寫為:

double divide(double x, double y)
{
    if (y == 0.)
    {
        FAIL("除數不能為0");    
            // 這一點要尤其注意,一定要將FAIL放在if判斷的括號內部 
            // 如果不這樣做的話,if 預設後面的一條語句作為你if判斷成立時,要執行的動作
            // 這樣FAIL巨集原樣替換的話,就無法識別oss << msg; 中的oss了
    }
    return x / y;
}

因為FAIL巨集執行的動作是throw拋異常,如果不對之進行捕獲的話,將由編譯器進行捕獲:

int main(int, char**)
{
    double x = 1., y = 0.;
    divide(x, y);
    return 0;
}

編譯器將彈出如下視窗:


這裡寫圖片描述

int main(int, char**)
{
    try
    {
        double x = 1., y = 0.;
        divide(x, y);
    }
    catch(exception& e)
    {
        cout << e.what() << endl;
    }
    return 0;
}

或者我們使用斷言的方式:

int main(int, char**)
{

    try
    {
        double x = 1., y = 0.;
        ASSERT(y != 0., "除數不能為0");
    }
    catch(exception& e)
    {
        cout << e.what() << endl;
    }
    return 0;
}

相關文章