[原創]如何寫一個完善的c++異常處理類
我們的異常處理類的features
如何寫一個異常處理類是一個不太容易的事情,最近剛好接觸了一些不錯的代碼,看到了一些技巧,這里和大家分享一下。
一個相對完善的異常處理類(以及附加的一些東西)應該能夠處理下面的一些功能:
1) 能夠方便的定義異常類的繼承樹
2) 能夠方便的throw、catch,也就是在代碼中捕獲、處理代碼的部分應該更短
3) 能夠獲取異常出現的源文件的名字、方法的名字、行號
4) 能夠獲取異常出現的調用棧并且打印出來
由于目前我用的平臺是linux,所以里面調用的一些函數也只是在linux下面有用。Windows也肯定是具有相應的函數的,具體可能需要去查查
首先科普一些內容:
1) 對于沒有捕獲的異常(no handler),則會終止程序,調用terminate()
2) 在定義函數的時候,我們可以在定義的后面加上throw (exception1, exception2…):
a) 如果沒有寫這一段、則可能拋出任意的異常
b) 如果寫throw(),則表示函數不能拋出任意的異常
c) 如果寫throw(A, B), 表示函數拋出A、B的異常
如果拋出的異常不在列表范圍內,則異常不能被catch,也就會調用terminate()
我們構想一下我們定義、調用我們的異常類的時候是怎樣的一個情形:
1) 定義:
class DerivedException : public BaseException { public: MY_DEFINE_EXCEPTION(DerivedException, BaseException); };
2) 如何拋出異常
MY_THROW(DerivedException)
3) 如何catch異常
catch (DerivedException& e) { cout<< e.what() << endl; }
這個輸出的內容包括錯誤的行號、文件名、方法名、和調用棧的列表
給出我們異常類的頭文件:
#ifndef EXCEPTION_TEST #define EXCEPTION_TEST #include <exception> #include <string> #define MY_THROW(ExClass, args...) \ do \ { \ ExClass e(args); \ e.Init(__FILE__, __PRETTY_FUNCTION__, __LINE__); \ throw e; \ } \ while (false) #define MY_DEFINE_EXCEPTION(ExClass, Base) \ ExClass(const std::string& msg = "") throw() \ : Base(msg) \ {} \ \ ~ExClass() throw() {} \ \ /* override */ std::string GetClassName() const \ { \ return #ExClass; \ } class ExceptionBase : public std::exception { public: ExceptionBase(const std::string& msg = "") throw(); virtual ~ExceptionBase() throw(); void Init(const char* file, const char* func, int line); virtual std::string GetClassName() const; virtual std::string GetMessage() const; const char* what() const throw(); const std::string& ToString() const; std::string GetStackTrace() const; protected: std::string mMsg; const char* mFile; const char* mFunc; int mLine; private: enum { MAX_STACK_TRACE_SIZE = 50 }; void* mStackTrace[MAX_STACK_TRACE_SIZE]; size_t mStackTraceSize; mutable std::string mWhat; }; class ExceptionDerived : public ExceptionBase { public: MY_DEFINE_EXCEPTION(ExceptionDerived, ExceptionBase); }; #endif
這個頭文件首先定義了兩個宏,這里先暫時不管他,我先來解釋一下ExceptionBase,它繼承自std::exception,std::exception里面其實已經提供了一些功能了,但是比較弱,為了實現我們上文提到的功能,這里只是繼承了std:exception的借口,也就是what()函數。
上面的接口應該比較好理解,45行的GetStackTrace是打印當前的調用棧,49-51行分別存儲了當前出現exception的源文件名,函數名,行號,54行定義了最大的調用棧顯示的深度,也就是顯示50行。
60行顯示了怎樣定義一個新的異常類,這個就很方便了,通過MY_DEFINE_EXCEPTION宏去定義了一個繼承類,詳情見16行,這里不再細說,我這里想說說7行的MY_THROW宏,使用了3個內置的參數,__FILE__, __LINE__, __PRETTY_FUNCTION__, 他們分別是當前的文件名,行號,和函數名,他們的使用方法是在哪兒出現,其相應的值就是什么。
為什么這里要使用MY_THROW宏呢?其實是為了方便的把行號、文件名等加入進來,宏展開的時候是在一行上的,這樣也使得行號與出錯的行號保持一致,而且讓代碼更簡單。
給出異常類的.cpp文件:
#include <execinfo.h> #include <stdlib.h> #include <cxxabi.h> #include <iostream> #include <sstream> #include "exception_test.h" using namespace std; ExceptionBase::ExceptionBase(const std::string& msg) throw() : mMsg(msg), mFile("<unknown file>"), mFunc("<unknown func>"), mLine(-1), mStackTraceSize(0) {} ExceptionBase::~ExceptionBase() throw() {} void ExceptionBase::Init(const char* file, const char* func, int line) { mFile = file; mFunc = func; mLine = line; mStackTraceSize = backtrace(mStackTrace, MAX_STACK_TRACE_SIZE); } std::string ExceptionBase::GetClassName() const { return "ExceptionBase"; } const char* ExceptionBase::what() const throw() { return ToString().c_str(); } const std::string& ExceptionBase::ToString() const { if (mWhat.empty()) { stringstream sstr(""); if (mLine > 0) { sstr << mFile << "(" << mLine << ")"; } sstr << ": " << GetClassName(); if (!GetMessage().empty()) { sstr << ": " << GetMessage(); } sstr << "\nStack Trace:\n"; sstr << GetStackTrace(); mWhat = sstr.str(); } return mWhat; } std::string ExceptionBase::GetMessage() const { return mMsg; } std::string ExceptionBase::GetStackTrace() const { if (mStackTraceSize == 0) return "<No stack trace>\n"; char** strings = backtrace_symbols(mStackTrace, 10); if (strings == NULL) // Since this is for debug only thus // non-critical, don't throw an exception. return "<Unknown error: backtrace_symbols returned NULL>\n"; std::string result; for (size_t i = 0; i < mStackTraceSize; ++i) { std::string mangledName = strings[i]; std::string::size_type begin = mangledName.find('('); std::string::size_type end = mangledName.find('+', begin); if (begin == std::string::npos || end == std::string::npos) { result += mangledName; result += '\n'; continue; } ++begin; int status; char* s = abi::__cxa_demangle(mangledName.substr(begin, end-begin).c_str(), NULL, 0, &status); if (status != 0) { result += mangledName; result += '\n'; continue; } std::string demangledName(s); free(s); // Ignore ExceptionBase::Init so the top frame is the // user's frame where this exception is thrown. // // Can't just ignore frame#0 because the compiler might // inline ExceptionBase::Init. result += mangledName.substr(0, begin); result += demangledName; result += mangledName.substr(end); result += '\n'; } free(strings); return result; } /* * test-main */ int f2() { MY_THROW(ExceptionDerived, "f2 throw"); } void f1() { try { f2(); } catch (ExceptionDerived& e) { cout << e.what() << endl; } } int main() { f1();
117行后展示了一個測試代碼,代碼雖然定義比較麻煩,不過使用還是很方便的:)。