簡體   English   中英

如何在運行時確定是否存在特定C ++異常類的catch塊?

[英]How can I determine at runtime whether there is a catch block for a particular C++ exception class?

在Linux上,我希望能夠確定特定類異常(或該類的超類)的catch塊當前是否在范圍內(忽略捕獲所有塊)。

特別是,我希望能夠實現isThereACatchBlock函數的行為如下:

bool isThereACatchBlock( std::type_info const & ti ) {
    ...;
}

class MyException {
};

class MyDerivedException : public MyException {
};

class MyOtherException {
};

void f() {
    try {
        isThereACatchBlock( typeid( MyException ) ); // Should return true
        isThereACatchBlock( typeid( MyDerivedException ) ); // Should return true
        isThereACatchBlock( typeid( MyOtherException ) ); // Should return false
   } catch( MyException const & e ) {
   } catch( ... ) {
   }
}

我知道,該系統具有此信息,以便它可以正確地實現異常處理-我相信這是存儲在.eh_frame和/或.gcc_except_table部分,如在這個崗位 但是,我不確定程序是否有任何簡單的方法來解釋該信息。 有人可以幫忙嗎?

閱讀您的一條評論,我看到您想要這樣做的一個原因是為了避免為處理的異常生成回溯,但是未處理的異常,您希望進行回溯。

如果這就是你想要這樣做的原因,你可以使用std::set_terminate()來設置一個終止處理程序,當發生未處理的異常時會調用它。 使用我自己的回溯處理程序進行測試,回溯顯示跟蹤一直到導致失敗的throw(),因為throw實現了異常不會被捕獲后直接調用終止處理程序。

注意,這只會從最近一次投擲中捕獲堆棧,直到它終止。 如果捕獲然后重新拋出異常,則初始throw和catch之間的堆棧將展開並且不再可用。

int foo() {
  throw std::runtime_error("My error");    
}
std::terminate_handler old_term_func;
void term_func() {      
  // Insert backtrace generation/output here
  old_term_func();
}

void test() {
  try {
    foo();
  } catch (const std::runtime_error& e) {
    std::cout <<"Exception caught, no backtrace generated" << std::endl;
  }    
  foo(); // Exception not caught, will call term_func    
}
int main() {
  old_term_func = std::set_terminate( term_func ) ;
  test();
}

你可以通過制作一個“金絲雀”過程來測試發生的事情並報告結果,從而以一種非常流行的方式解決這個問題。 我把一個“概念證明”的例子放在一起:

#include <exception>
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>

struct SomeException : public std::exception {
};

template <typename E>
struct isThereACatch {
  isThereACatch() : result(doTest()) {
  }

  operator bool() const {
    return result;
  }

private:
  const bool result;

  struct TestException : public E {
    TestException(int f) {
      fd = f;
    }

    virtual ~TestException() throw() { 
      notify(true);
      exit(0);
    }

    static void notify(bool result) {
      const ssize_t ret = write(fd, &result, sizeof(result));
      assert(sizeof(bool) == ret);
    }

    static void unhandled() {
      notify(false);
      exit(0);
    }

    static int fd;
  };

  static bool doTest() {
    int pipes[2];
    const int ret = pipe(pipes);
    assert(!ret);
    const pid_t pid = fork();
    if (pid) {
      // we're parent, wait for the child to return
      bool caught;
      const ssize_t ret = read(pipes[0], &caught, sizeof(caught));
      assert(sizeof(bool) == ret);
      int status;
      waitpid(pid, &status, 0);
      return caught;
    }
    else {
      // if we are the child (i.e. pid was 0) use our own default handler
      std::set_terminate(TestException::unhandled);
      // Then throw one and watch
      throw TestException(pipes[1]);
    }
  }
};

template <typename E>
int isThereACatch<E>::TestException::fd;

int main() {
  try {
    isThereACatch<std::exception> e1;
    isThereACatch<SomeException> e2;
    std::cout << "std::exception - "  << e1 << std::endl;
    std::cout << "SomeException - " << e2 << std::endl;
  }
  catch (const SomeException& ex) {
  }
  std::cout << "Still running..."  << std::endl;
}

它的優點是它是半便攜式的。 我會憤怒地使用它嗎? 可能不是。 我最關心的是,一些奇怪和奇妙(但意外)事物的例外可能會產生重大的副作用。 例如,刪除文件或更糟糕的析構函數。 您還需要確保您測試的異常是默認可構造的而不是基本類型。 我的例子的另一個問題是線程安全,不僅僅是TestException的普通fd靜態成員 - 你可能需要在canary進程運行時使任何其他線程掛起。

免責聲明:這樣做可能是一個壞主意。

我的想法如下。 請記住,我正在為此動態編寫所有代碼,因此可能並不完美。 :)

它實際上可能更容易在代碼中解釋,但我會先嘗試給出要點。 因為你對catch (...)不感興趣,所以我沒有專注於檢測它,但是,我認為修改這個想法也是相對容易的。 (注意:最初我打算使用一個指向函數的指針作為判斷你所處的函數的方法,但我最終得到了這個名字,因為我沒有考慮虛函數。我相信這可以如果有必要,所有都可以優化。)

創建以下內容:

  • 具有所需函數的靜態版本的類
  • 一種特殊的自定義“堆棧”類型,用於保存您的異常信息,其操作可以根據該信息拆除堆棧
  • 包含類型字符串和 void指針 字符串的結構,用於保存其創建的函數的名稱

設定:

  • try之前,放置一個catch類型結構,其中包含要捕獲的類型名稱,以及堆棧中此函數中捕獲的所有異常,以及 指向 函數名稱的 指針
  • 要為類型設置的字符串是通過引用類型來確定的(因此"..."對於默認捕獲是正常的)。
    • 最初我正在玩弄使用typeid獲取未修飾的類型名稱的想法,然后使用.raw_name()來獲取.raw_name()的名稱
    • 但它不適用於本機類型或非虛擬類型,並且實際上不需要修改名稱,因此對於此實現它是沒有意義的

拆除:

  • 在每個catch區域中,在頂部,將堆棧向下撕掉一個超出您正在捕獲的類型的堆棧
  • 在函數中的最后一個catch塊之后,將堆棧向下撕掉一個超出最后一個catch中的第一個拆卸實例

該解決方案的主要問題是它顯然非常麻煩。

一個解決方案是[打賭你看到了這個]宏。

ExceptionStackHandler.h

// ... 
// declaration of the class with the needed functions, perhaps
// inline definitions. the declaration of the stack. etc.
// ...
#if __STDC__ && __STDC_VERSION__ >= 199901L
    #define FN_NAME __func__
#else
    #define FN_NAME __FUNCTION__
#endif

// was thinking would be more to this; don't think we need it
//#define try_code(code) try { code } 

// this macro wraps the code such that expansion is not aborted 
// if there happen to be commas in the code.
#define protect(code) if (true) { code }

// normal catch and processing
#define catch_code(seed_code, catch_type, catch_code) \
    ExceptionStackHandler.Stack.Push(exceptionItem(#catch_type, FN_NAME)); \
    seed_code \
    catch (catch_type Ex) \
    { \
        ExceptionStackHandler.Stack.PopThrough(#catch_type, FN_NAME); \
        catch_code \
    } 

// you *must* close a try with one of the following two calls, otherwise
// some items may be missed when clearing out the stack

// catch of all remaining types
#define close_catchall(seed_code, last_catch_type, catch_code) \
    seed_code \
    catch (...) \
    { \
        ExceptionStackHandler.Stack.PopThrough(#last_catch_type, FN_NAME); \
        catch_code \
    } \
    ExceptionStackHandler.Stack.PopThrough(#last_catch_type, FN_NAME); \

// cleanup of code without catching remaining types
#define close_nocatch(last_catch_type, catch_code) \
    seed_code \
    ExceptionStackHandler.Stack.PopThrough(#last_catch_type, FN_NAME)

然后在你的代碼中它看起來像

bool isTheRoofOnFire(bool& isWaterNeeded)
{
    // light some matches, drip some kerosene, drop a zippo

    close_nocatch
    (
    catch_code
    (
    catch_code 
    (
    //--------------------------------------------------------
    // try block for the roof
    try
    {
        protect (
            // we don't need no water
            if (isWaterNeeded)
                isWaterNeeded = false;
        )
    }
    // End Try Block
    //--------------------------------------------------------
    ,
    //--------------------------------------------------------
    // catch(string Ex)
    string,
    protect (
      if (Ex == "Don't let it burn!")
          isWaterNeed = true;

      throw "I put the water on the fire anyway.";
    )
    )
    // END - catch (string Ex) 
    //--------------------------------------------------------
    ,
    //--------------------------------------------------------
    // catch(RoofCollapsedException Ex)
    RoofCollapsedException
    try_code (
        protect (
            if (RoofCollapsedException.isAnythingWeCanDo == false)
                throw new TooLateException(RoofCollapsedException);
            else
                isWaterNeeded = true;
        )
    )
    // END - catch(RoofCollapsedException Ex)
    //--------------------------------------------------------
    )
    // closing without catchall exception handler
    //--------------------------------------------------------
}

現在,我承認,這很難看。 Reeeal難看。 我確信有更好的方法來編寫這些宏,但作為一個理論上的概念驗證,我認為沒有任何東西可行。 但真正的解決方案不能這么難。 它結果不好的原因並不是那個丑陋的想法。 只是宏不能以干凈的方式實現它。 由於它是如此規則的模式,所以應該有一種方法可以在不觸及源代碼的情況下實現它。 如果只有C預處理器不是唯一的選擇......

;)所以。 其實我覺得可能有。 一個更好的解決方案是使用功能更強大的預處理器,通過允許編譯甚至無需額外預處理(例如指令作為注釋),可以提供更清晰的C ++代碼。 我認為用CS-Script (它將在Mono下運行)這樣的工具寫一些內容會相當容易,我相信一些例子包含在'預編譯器'過程的文檔中,它允許你這樣做。 而且,實際上,對此:你甚至不需要指令。 指令很酷,但您不需要通用宏處理器來執行您需要的操作。 當然,不用說你可以在任何有能力處理文本文件的東西中編寫它。

雖然我還沒有嘗試過實現它,但我認為這可能只是一個處理器,它運行在整個文件組上,不需要直接修改代碼。 (找到文件中的所有try/catch塊,收集類型,創建額外的語句,然后寫出文件。)也許移動Makefile從中提取構建文件的目錄,然后在編譯之前處理所有文件和將輸出放在新的build子目錄中。 我打賭LINQ可以在一些小語句中做到這一點,雖然這並不意味着可以編寫LINQ。 :)我仍然打賭它不是那么大的任務,並且它將是實現解決方案的完美方式; 在類中定義堆棧,類和靜態檢查器函數。

這讓我想起......“完整性”:

bool ExceptionStackHandling::isThereACatchBlock(string Type)
{
    return (ExceptionStackHandling.Stack.peekOnType(Type) > 0);
}

所以,最后:我很難想象處理像你最終得到的代碼的代碼。 現在,我沒有縮進,我想它會變得半更半可讀,但問題剛剛發生了變化:現在如果你有七個異常類型被處理,你有7個縮進將所有內容推離屏幕。 但我確實認為可以通過一個簡單的外部應用程序來完成,它可以自動完成所有操作。

我不知道你怎么能檢查哪些catch塊存在,它可能需要很大的努力,如果你改變了相同編譯器的次要版本,可能會破壞。 您的注釋表明您真正想要的是在異常的構造函數中獲取堆棧跟蹤,而不是它們被捕獲的位置。

使用backtracebacktrace_symbols函數在linux / gcc中實際上很容易做到GCC很樂意為你提供堆棧轉儲。 另請參閱: 手冊頁此SO問題 (請注意問題是關於崩潰,但您可以隨時在程序中執行此操作,無論崩潰與否)。

即使異常被某些代碼(或其他)捕獲,它仍會生成堆棧轉儲,但它會讓代碼繼續運行而不是調用abort()或terminate()。 但你可以通過日志查看哪一個導致你的問題,你不應該有那么多(如果你這樣做,你可能使用異常錯誤...他們是if / else / while或者返回的可憐的替代品有時是錯誤代碼),

“我知道系統有這些信息,以便它可以正確地實現異常處理”

那是不真實的。 系統可以為異常處理程序使用真正的堆棧,即只能訪問頂級異常處理程序的堆棧。 在C ++中,您永遠不需要知道是否存在另一個異常處理程序,然后才能確定是否輸入了頂級異常處理程序。 拋出的異常由頂級處理程序處理,並且您使用它,或者它沒有被處理,並且您彈出頂部異常處理程序未使用。

在您的情況下,運行時可能看到的唯一異常處理程序因此是catch( MyException ) 那意味着你無法知道isThereACatchBlock( typeid( MyOtherException ) ); 應該是false 訪問異常處理程序“后面” catch( MyException )的唯一方法可能是throw一個catch( MyException )處理的異常。

你可以玩重新拋出......

void isThereACatchBlock(bool& p_ret)
{
    try
    {
        throw;
    }
    catch(const MyException& p_ex)
    {
        p_ret = true;
        throw;
    }
    catch(const MyOtherException& p_ex)
    {
        p_ret =false;
        throw;
    }
    catch(...)
    {
        p_ret = false;
        throw;
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM