[原创]如何写一个完善的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行后展示了一个测试代码,代码虽然定义比较麻烦,不过使用还是很方便的:)。