我刚刚完成了一个C ++程序,我已经实现了自己的异常(虽然派生自std :: exception)。当一个异常引起连锁反应,向上传播错误并引起其他异常时,我应用的做法是在模块的每个适当步骤(读取类)中连接错误消息。即删除旧的异常本身并创建一个新的异常,但错误消息更长。
这对我的小程序可能有用,但最后我的方法并不是很满意。例如,除最后一个异常外,不保留行号(虽然目前不应用)和文件名;而且在第一个例外情况下,这些信息最为重要。
我认为通过将异常链接在一起可以更好地处理这个问题;即旧异常在新异常的构造函数中提供。但是如何实施呢?当它们超出方法的范围时,异常是否会死亡,从而阻止使用异常指针?如果异常可以是任何派生类,如何复制和存储异常?
这最终促使我考虑在C ++中链接异常是否是一个好主意。或许应该创建一个例外,然后添加额外的数据(就像我一直在做的那样,但可能会以更好的方式)?
您对此有何回应?是否应将由另一个引起的异常链接在一起以保留某种“异常追踪” - 以及如何实施? - 或者是否应该使用单个例外并附加其他数据 - 以及应该如何进行?
答案 0 :(得分:4)
由于已经提出这个问题,因此使用C ++ 11对标准进行了可观的更改。 我在关于异常的讨论中不断遗漏这一点,但是下面的方法,嵌套异常,可以解决这个问题:
std::nested_exception
和std::throw_with_nested
StackOverflow here和here中描述了如何在代码中获取异常的回溯,而无需调试器或繁琐的日志记录,只需简单编写一个适当的异常处理程序,它将重新抛出嵌套的异常。
由于您可以对任何派生的异常类执行此操作,因此可以向此类回溯添加大量信息! 您还可以查看我的MWE on GitHub,其中回溯看起来像这样:
Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"
答案 1 :(得分:1)
除了catch
的重新抛出之外,如果您希望它比接收它的throw;
块更长,则需要将数据从异常对象复制到链中。 (例如,如果catch
阻止通过throw obj;
退出,则包含该内容。)
这可以通过将要保存的数据放在堆上,并在异常内的私有数据上实现swap
(move
C ++ 0x)来完成,例如。
当然,在使用具有异常的堆时需要小心......但是在大多数现代操作系统中,内存过量使用会完全阻止new
抛出,无论好坏。在完全崩溃时,良好的内存容限和从链中丢弃异常应该保证其安全。
struct exception_data { // abstract base class; may contain anything
virtual ~exception_data() {}
};
struct chained_exception : std::exception {
chained_exception( std::string const &s, exception_data *d = NULL )
: data(d), descr(s) {
try {
link = new chained_exception;
throw;
} catch ( chained_exception &prev ) {
swap( *link, prev );
} // catch std::bad_alloc somehow...
}
friend void swap( chained_exception &lhs, chained_exception &rhs ) {
std::swap( lhs.link, rhs.link );
std::swap( lhs.data, rhs.data );
swap( lhs.descr, rhs.descr );
}
virtual char const *what() const throw() { return descr.c_str(); }
virtual ~chained_exception() throw() {
if ( link && link->link ) delete link; // do not delete terminator
delete data;
}
chained_exception *link; // always on heap
exception_data *data; // always on heap
std::string descr; // keeps data on heap
private:
chained_exception() : link(), data() {}
friend int main();
};
void f() {
try {
throw chained_exception( "humbug!" );
} catch ( std::exception & ) {
try {
throw chained_exception( "bah" );
} catch ( chained_exception &e ) {
chained_exception *ep = &e;
for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
std::cerr << ep->what() << std::endl;
}
}
}
try {
throw chained_exception( "meh!" );
} catch ( chained_exception &e ) {
for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
std::cerr << ep->what() << std::endl;
}
}
}
int main() try {
throw chained_exception(); // create dummy end-of-chain
} catch( chained_exception & ) {
// body of main goes here
f();
}
输出(适当脾气暴躁):
bah
humbug!
meh!
答案 2 :(得分:1)
您可能希望看一下:http://www.boost.org/doc/libs/1_43_0/libs/exception/doc/boost-exception.html
这与MS在C#中所做的有些不同,但似乎符合您的要求。
答案 3 :(得分:0)
另一个想法是将相关数据添加到您的异常对象,然后使用裸throw;
语句重新抛出它。我认为在这种情况下会保留堆栈信息,因此您仍然会知道异常的原始来源,但测试将是一个好主意。
我敢打赌,由于任何堆栈信息是否完全可用是实现定义的,因此无论是否在裸throw;
语句之后以任何方式保留它,实现都会有更大的变化。