我们的异常处理类的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: class DerivedException : public BaseException
2: {
3: public:
4: MY_DEFINE_EXCEPTION(DerivedException, BaseException);
5: };
2) 如何抛出异常
1: MY_THROW(DerivedException)
3) 如何catch异常
1: catch (DerivedException& e)
2: {
3: cout<< e.what() << endl;
4: }
这个输出的内容包括错误的行号、文件名、方法名、和调用栈的列表
给出我们异常类的头文件:
1: #ifndef EXCEPTION_TEST 2: #define EXCEPTION_TEST 3: 4: #include <exception> 5: #include <string> 6: 7: #define MY_THROW(ExClass, args...) \ 8: do \ 9: { \ 10: ExClass e(args); \ 11: e.Init(__FILE__, __PRETTY_FUNCTION__, __LINE__); \ 12: throw e; \ 13: } \ 14: while (false) 15: 16: #define MY_DEFINE_EXCEPTION(ExClass, Base) \ 17: ExClass(const std::string& msg = "") throw() \ 18: : Base(msg) \ 19: {} \ 20: \ 21: ~ExClass() throw() {} \ 22: \ 23: /* override */ std::string GetClassName() const \ 24: { \ 25: return #ExClass; \ 26: } 27: 28: class ExceptionBase : public std::exception
29: { 30: public: 31: ExceptionBase(const std::string& msg = "") throw(); 32: 33: virtual ~ExceptionBase() throw(); 34: 35: void Init(const char* file, const char* func, int line); 36: 37: virtual std::string GetClassName() const; 38: 39: virtual std::string GetMessage() const; 40: 41: const char* what() const throw(); 42: 43: const std::string& ToString() const; 44: 45: std::string GetStackTrace() const; 46: 47: protected: 48: std::string mMsg; 49: const char* mFile; 50: const char* mFunc; 51: int mLine; 52: 53: private: 54: enum { MAX_STACK_TRACE_SIZE = 50 }; 55: void* mStackTrace[MAX_STACK_TRACE_SIZE]; 56: size_t mStackTraceSize; 57: mutable std::string mWhat; 58: }; 59: 60: class ExceptionDerived : public ExceptionBase 61: { 62: public: 63: MY_DEFINE_EXCEPTION(ExceptionDerived, ExceptionBase); 64: }; 65: 66: #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: #include <execinfo.h> 2: #include <stdlib.h> 3: #include <cxxabi.h> 4: 5: #include <iostream> 6: #include <sstream> 7: 8: #include "exception_test.h" 9: 10: using namespace std; 11: 12: ExceptionBase::ExceptionBase(const std::string& msg) throw() 13: : mMsg(msg), 14: mFile("<unknown file>"), 15: mFunc("<unknown func>"), 16: mLine(-1), 17: mStackTraceSize(0) 18: {} 19: 20: ExceptionBase::~ExceptionBase() throw() 21: {} 22: 23: void ExceptionBase::Init(const char* file, const char* func, int line) 24: { 25: mFile = file; 26: mFunc = func; 27: mLine = line; 28: mStackTraceSize = backtrace(mStackTrace, MAX_STACK_TRACE_SIZE); 29: } 30: 31: std::string ExceptionBase::GetClassName() const 32: { 33: return "ExceptionBase"; 34: } 35: 36: const char* ExceptionBase::what() const throw() 37: { 38: return ToString().c_str(); 39: } 40: 41: const std::string& ExceptionBase::ToString() const 42: { 43: if (mWhat.empty()) 44: { 45: stringstream sstr(""); 46: if (mLine > 0) 47: { 48: sstr << mFile << "(" << mLine << ")"; 49: } 50: sstr << ": " << GetClassName(); 51: if (!GetMessage().empty()) 52: { 53: sstr << ": " << GetMessage(); 54: } 55: sstr << "\nStack Trace:\n"; 56: sstr << GetStackTrace(); 57: mWhat = sstr.str(); 58: } 59: return mWhat; 60: } 61: 62: std::string ExceptionBase::GetMessage() const 63: { 64: return mMsg; 65: } 66: 67: std::string ExceptionBase::GetStackTrace() const 68: { 69: if (mStackTraceSize == 0) 70: return "<No stack trace>\n"; 71: char** strings = backtrace_symbols(mStackTrace, 10); 72: if (strings == NULL) // Since this is for debug only thus 73: // non-critical, don't throw an exception. 74: return "<Unknown error: backtrace_symbols returned NULL>\n"; 75: 76: std::string result; 77: for (size_t i = 0; i < mStackTraceSize; ++i) 78: { 79: std::string mangledName = strings[i]; 80: std::string::size_type begin = mangledName.find('('); 81: std::string::size_type end = mangledName.find('+', begin); 82: if (begin == std::string::npos || end == std::string::npos) 83: { 84: result += mangledName; 85: result += '\n'; 86: continue; 87: } 88: ++begin; 89: int status; 90: char* s = abi::__cxa_demangle(mangledName.substr(begin, end-begin).c_str(), 91: NULL, 0, &status); 92: if (status != 0) 93: { 94: result += mangledName; 95: result += '\n'; 96: continue; 97: } 98: std::string demangledName(s); 99: free(s); 100: // Ignore ExceptionBase::Init so the top frame is the 101: // user's frame where this exception is thrown 102: / 103: // Can't just ignore frame#0 because the compiler might 104: // inline ExceptionBase::Init. 105: result += mangledName.substr(0, begin); 106: result += demangledName; 107: result += mangledName.substr(end); 108: result += '\n'; 109: } 110: free(strings); 111: return result; 112: } 113: 114: /* 115: * test-main 116: */ 117: int f2() 118: { 119: MY_THROW(ExceptionDerived, "f2 throw"); 120: } 121: void f1() 122: { 123: try 124: { 125: f2(); 126: } 127: catch (ExceptionDerived& e) 128: { 129: cout << e.what() << endl; 130: } 131: } 132: int main() 133: { 134: f1(); 135: }
这是函数的实现代码,其他的都比较好理解,67行的GetStackTrace是相对复杂一点的,里面用backtrace函数去获取了当前调用栈的层数,用backtrace_symbols去获取当前调用栈的符号,而且__cxa_demangle函数的使用也值得去看看,这里不再细说了。
117行后展示了一个测试代码,代码虽然定义比较麻烦,不过使用还是很方便的:)。