zoukankan      html  css  js  c++  java
  • [原创]如何写一个完善的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) 定义:

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

    有什么问题意见欢迎大家指教,回复,站内信都可以

  • 相关阅读:
    Debian 添加Apache2
    最全面试资源,题库
    vue中的坑
    javascript事件相关4
    javascript事件相关3
    javascript事件相关2
    javascript事件学习笔记
    javascript 点点滴滴 jquery
    javascript 点点滴滴 jquery
    三栏自适应布局解决方案
  • 原文地址:https://www.cnblogs.com/LeftNotEasy/p/1865364.html
Copyright © 2011-2022 走看看