在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部分中,如this post中所述。但是,我不确定程序是否有任何简单的方法来解释该信息。有人可以帮忙吗?
答案 0 :(得分:4)
阅读您的一条评论,我看到您想要这样做的一个原因是避免为处理的异常生成回溯,但是未处理的异常,您希望进行回溯。
如果这就是你想要这样做的原因,你可以使用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();
}
答案 1 :(得分:2)
你可以通过制作一个“金丝雀”过程来测试发生的事情并报告结果,从而以一种非常流行的方式解决这个问题。我把一个“概念证明”的例子放在一起:
#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;
}
它具有半便携性的优点。我会愤怒地使用它吗?可能不是。我最关心的是,一些奇怪和奇妙(但意外)事物的例外可能会产生重大的副作用。例如。删除文件或更糟糕的析构函数。您还需要确保您测试的异常是默认可构造的而不是基本类型。我的例子的另一个问题是线程安全,不仅仅是fd
的普通TestException
静态成员 - 你可能需要在金丝雀进程运行时使任何其他线程挂起。
免责声明:这样做可能是一个坏主意。
答案 2 :(得分:2)
我的想法如下。请记住,我正在为此动态编写所有代码,因此可能并不完美。 :)
实际上在代码中解释可能更容易,但我会先尝试给出要点。由于你对catch (...)
不感兴趣,我没有专注于检测,但我认为修改这个想法也是相对容易的。 (注意:最初我打算使用一个指向函数的指针作为判断你所处的函数的方法,但我最终得到了这个名字,因为我没有考虑虚函数。我相信这可以如果有必要,所有这些都会得到优化。)
创建以下内容:
设定:
try
之前,放置一个捕获类型结构,其中包含要捕获的类型名称,以及堆栈中此函数中捕获的所有异常,以及"..."
适用于默认捕获)。
typeid
获取未修饰的类型名称,然后使用.raw_name()
获取受损名称拆解:
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个缩进将所有内容推离屏幕。但我确实认为可以通过一个简单的外部应用程序完成一些工作,它可以自动为您完成。
答案 3 :(得分:1)
我不知道你怎么能检查存在哪些catch块,它可能需要付出很大的努力,如果你改变了相同编译器的次要版本,它可能会中断。您的注释表明您真正想要的是在异常的构造函数中获取堆栈跟踪,而不是它们被捕获的位置。
使用backtrace
和backtrace_symbols
函数在linux / gcc中实际上很容易做到GCC很乐意为您提供堆栈转储。另请参阅:the man page和this SO question(请注意问题是关于崩溃,但您可以随时在程序中执行此操作,无论崩溃与否)。
即使异常被某些代码(或其他)捕获,它仍会生成堆栈转储,但它会让代码继续运行而不是调用abort()或terminate()。但你可以通过日志查看哪一个导致你的问题,你不应该有那么多(如果你这样做,你可能使用异常错误...他们是if / else / while或者返回的可怜的替代品有时会出现错误代码,
答案 4 :(得分:0)
“我知道系统有这些信息,以便它能正确实现异常处理”
这是不真实的。系统可以为异常处理程序使用真正的堆栈,即只能访问顶级异常处理程序的堆栈。在C ++中,您永远不需要知道是否存在另一个异常处理程序,然后才能确定是否输入了顶级异常处理程序。抛出的异常由顶级处理程序处理,并且您使用它,或者它没有被处理,并且您弹出顶部异常处理程序未使用。
在您的情况下,运行时可能看到的唯一异常处理程序是catch( MyException )
。这意味着您无法知道isThereACatchBlock( typeid( MyOtherException ) );
应该是false
。访问异常处理程序“catch( MyException )
”的唯一方法可能是throw
catch( MyException )
未处理的异常。
答案 5 :(得分:-1)
你可以玩重新抛出......
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;
}
}