[原創]如何寫一個完善的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行后展示了一個測試代碼,代碼雖然定義比較麻煩,不過使用還是很方便的:)。
留言列表
