[原創]如何寫一個完善的c++異常處理類

作者: LeftNotEasy  來源: 博客園  發布時間: 2010-11-03 16:28  閱讀: 3085 次  推薦: 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++ 異常處理
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()