您的位置:知识库 » 编程语言

[原创]如何写一个完善的c++异常处理类

作者: LeftNotEasy  来源: 博客园  发布时间: 2010-11-03 16:28  阅读: 3323 次  推荐: 2   原文链接   [收藏]  
摘要:如何写一个异常处理类是一个不太容易的事情,最近刚好接触了一些不错的代码,看到了一些技巧,这里和大家分享一下。

  我们的异常处理类的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();

  这是函数的实现代码,其他的都比较好理解,67行的GetStackTrace是相对复杂一点的,里面用backtrace函数去获取了当前调用栈的层数,用backtrace_symbols去获取当前调用栈的符号,而且__cxa_demangle函数的使用也值得去看看,这里不再细说了。

  117行后展示了一个测试代码,代码虽然定义比较麻烦,不过使用还是很方便的:)。

2
0
标签:c++ 异常处理

编程语言热门文章

    编程语言最新文章

      最新新闻

        热门新闻