我们的异常处理类的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) 定义:
public BaseException
2: {
public:
4: MY_DEFINE_EXCEPTION(DerivedException, BaseException);
5: };
2) 如何抛出异常
1: MY_THROW(DerivedException)
3) 如何catch异常
catch (DerivedException& e)
2: {
3: cout<< e.what() << endl;
4: }
这个输出的内容包括错误的行号、文件名、方法名、和调用栈的列表
给出我们异常类的头文件:
#ifndef EXCEPTION_TEST
#define EXCEPTION_TEST
3:
#include <exception>
#include <string>
6:
#define MY_THROW(ExClass, args...) \
do \
9: { \
10: ExClass e(args); \
11: e.Init(__FILE__, __PRETTY_FUNCTION__, __LINE__); \
throw e; \
13: } \
while (false)
15:
#define MY_DEFINE_EXCEPTION(ExClass, Base) \
throw() \
18: : Base(msg) \
19: {} \
20: \
throw() {} \
22: \
const \
24: { \
return #ExClass; \
26: }
27:
public std::exception
29: {
public:
throw();
32:
throw();
34:
int line);
36:
const;
38:
const;
40:
throw();
42:
const;
44:
const;
46:
protected:
48: std::string mMsg;
char* mFile;
char* mFunc;
int mLine;
52:
private:
enum { MAX_STACK_TRACE_SIZE = 50 };
void* mStackTrace[MAX_STACK_TRACE_SIZE];
56: size_t mStackTraceSize;
mutable std::string mWhat;
58: };
59:
public ExceptionBase
61: {
public:
63: MY_DEFINE_EXCEPTION(ExceptionDerived, ExceptionBase);
64: };
65:
#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>
4:
#include <iostream>
#include <sstream>
7:
9:
namespace std;
11:
throw()
13: : mMsg(msg),
),
),
16: mLine(-1),
17: mStackTraceSize(0)
18: {}
19:
throw()
21: {}
22:
int line)
24: {
25: mFile = file;
26: mFunc = func;
27: mLine = line;
28: mStackTraceSize = backtrace(mStackTrace, MAX_STACK_TRACE_SIZE);
29: }
30:
const
32: {
;
34: }
35:
throw()
37: {
return ToString().c_str();
39: }
40:
const
42: {
if (mWhat.empty())
44: {
);
if (mLine > 0)
47: {
;
49: }
<< GetClassName();
if (!GetMessage().empty())
52: {
<< GetMessage();
54: }
;
56: sstr << GetStackTrace();
57: mWhat = sstr.str();
58: }
return mWhat;
60: }
61:
const
63: {
return mMsg;
65: }
66:
const
68: {
if (mStackTraceSize == 0)
;
char** strings = backtrace_symbols(mStackTrace, 10);
// Since this is for debug only thus
// non-critical, don't throw an exception.
;
75:
76: std::string result;
for (size_t i = 0; i < mStackTraceSize; ++i)
78: {
79: std::string mangledName = strings[i];
'(');
'+', begin);
if (begin == std::string::npos || end == std::string::npos)
83: {
84: result += mangledName;
'\n';
continue;
87: }
88: ++begin;
int status;
char* s = abi::__cxa_demangle(mangledName.substr(begin, end-begin).c_str(),
91: NULL, 0, &status);
if (status != 0)
93: {
94: result += mangledName;
'\n';
continue;
97: }
98: std::string demangledName(s);
99: 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.
105: result += mangledName.substr(0, begin);
106: result += demangledName;
107: result += mangledName.substr(end);
'\n';
109: }
110: free(strings);
return result;
112: }
113:
/*
* test-main
*/
int f2()
118: {
);
120: }
void f1()
122: {
try
124: {
125: f2();
126: }
catch (ExceptionDerived& e)
128: {
129: cout << e.what() << endl;
130: }
131: }
int main()
133: {
134: f1();
135: }
这是函数的实现代码,其他的都比较好理解,67行的GetStackTrace是相对复杂一点的,里面用backtrace函数去获取了当前调用栈的层数,用backtrace_symbols去获取当前调用栈的符号,而且__cxa_demangle函数的使用也值得去看看,这里不再细说了。
117行后展示了一个测试代码,代码虽然定义比较麻烦,不过使用还是很方便的:)。
有什么问题意见欢迎大家指教,回复,站内信都可以
我们的异常处理类的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) 定义:
public BaseException
2: {
public:
4: MY_DEFINE_EXCEPTION(DerivedException, BaseException);
5: };
2) 如何抛出异常
1: MY_THROW(DerivedException)
3) 如何catch异常
catch (DerivedException& e)
2: {
3: cout<< e.what() << endl;
4: }
这个输出的内容包括错误的行号、文件名、方法名、和调用栈的列表
给出我们异常类的头文件:
#ifndef EXCEPTION_TEST
#define EXCEPTION_TEST
3:
#include <exception>
#include <string>
6:
#define MY_THROW(ExClass, args...) \
do \
9: { \
10: ExClass e(args); \
11: e.Init(__FILE__, __PRETTY_FUNCTION__, __LINE__); \
throw e; \
13: } \
while (false)
15:
#define MY_DEFINE_EXCEPTION(ExClass, Base) \
throw() \
18: : Base(msg) \
19: {} \
20: \
throw() {} \
22: \
const \
24: { \
return #ExClass; \
26: }
27:
public std::exception
29: {
public:
throw();
32:
throw();
34:
int line);
36:
const;
38:
const;
40:
throw();
42:
const;
44:
const;
46:
protected:
48: std::string mMsg;
char* mFile;
char* mFunc;
int mLine;
52:
private:
enum { MAX_STACK_TRACE_SIZE = 50 };
void* mStackTrace[MAX_STACK_TRACE_SIZE];
56: size_t mStackTraceSize;
mutable std::string mWhat;
58: };
59:
public ExceptionBase
61: {
public:
63: MY_DEFINE_EXCEPTION(ExceptionDerived, ExceptionBase);
64: };
65:
#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>
4:
#include <iostream>
#include <sstream>
7:
9:
namespace std;
11:
throw()
13: : mMsg(msg),
),
),
16: mLine(-1),
17: mStackTraceSize(0)
18: {}
19:
throw()
21: {}
22:
int line)
24: {
25: mFile = file;
26: mFunc = func;
27: mLine = line;
28: mStackTraceSize = backtrace(mStackTrace, MAX_STACK_TRACE_SIZE);
29: }
30:
const
32: {
;
34: }
35:
throw()
37: {
return ToString().c_str();
39: }
40:
const
42: {
if (mWhat.empty())
44: {
);
if (mLine > 0)
47: {
;
49: }
<< GetClassName();
if (!GetMessage().empty())
52: {
<< GetMessage();
54: }
;
56: sstr << GetStackTrace();
57: mWhat = sstr.str();
58: }
return mWhat;
60: }
61:
const
63: {
return mMsg;
65: }
66:
const
68: {
if (mStackTraceSize == 0)
;
char** strings = backtrace_symbols(mStackTrace, 10);
// Since this is for debug only thus
// non-critical, don't throw an exception.
;
75:
76: std::string result;
for (size_t i = 0; i < mStackTraceSize; ++i)
78: {
79: std::string mangledName = strings[i];
'(');
'+', begin);
if (begin == std::string::npos || end == std::string::npos)
83: {
84: result += mangledName;
'\n';
continue;
87: }
88: ++begin;
int status;
char* s = abi::__cxa_demangle(mangledName.substr(begin, end-begin).c_str(),
91: NULL, 0, &status);
if (status != 0)
93: {
94: result += mangledName;
'\n';
continue;
97: }
98: std::string demangledName(s);
99: 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.
105: result += mangledName.substr(0, begin);
106: result += demangledName;
107: result += mangledName.substr(end);
'\n';
109: }
110: free(strings);
return result;
112: }
113:
/*
* test-main
*/
int f2()
118: {
);
120: }
void f1()
122: {
try
124: {
125: f2();
126: }
catch (ExceptionDerived& e)
128: {
129: cout << e.what() << endl;
130: }
131: }
int main()
133: {
134: f1();
135: }
这是函数的实现代码,其他的都比较好理解,67行的GetStackTrace是相对复杂一点的,里面用backtrace函数去获取了当前调用栈的层数,用backtrace_symbols去获取当前调用栈的符号,而且__cxa_demangle函数的使用也值得去看看,这里不再细说了。
117行后展示了一个测试代码,代码虽然定义比较麻烦,不过使用还是很方便的:)。
有什么问题意见欢迎大家指教,回复,站内信都可以