[原创]如何写一个完善的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) 定义:
1 2 3 4 5 | class DerivedException : public BaseException { public : MY_DEFINE_EXCEPTION(DerivedException, BaseException); }; |
2) 如何抛出异常
1 | MY_THROW(DerivedException) |
3) 如何catch异常
1 2 3 4 | catch (DerivedException& e) { cout<< e.what() << endl; } |
这个输出的内容包括错误的行号、文件名、方法名、和调用栈的列表
给出我们异常类的头文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | #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文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | #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行后展示了一个测试代码,代码虽然定义比较麻烦,不过使用还是很方便的:)。