[英]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 (...)
不感興趣,所以我沒有專注於檢測它,但是,我認為修改這個想法也是相對容易的。 (注意:最初我打算使用一個指向函數的指針作為判斷你所處的函數的方法,但我最終得到了這個名字,因為我沒有考慮虛函數。我相信這可以如果有必要,所有都可以優化。)
創建以下內容:
設定:
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塊存在,它可能需要很大的努力,如果你改變了相同編譯器的次要版本,可能會破壞。 您的注釋表明您真正想要的是在異常的構造函數中獲取堆棧跟蹤,而不是它們被捕獲的位置。
使用backtrace
和backtrace_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.