因此,使用std::nested_exception
在C ++中嵌套异常的方法是:
void foo() {
try {
// code that might throw
std::ifstream file("nonexistent.file");
file.exceptions(std::ios_base::failbit);
}
catch(...) {
std::throw_with_nested(std::runtime_error("foo failed"));
}
}
但是这种技术在每个级别都使用显式的try / catch块,希望嵌套异常,这至少可以说是丑陋的。
RAII,Jon Kalb expands作为“责任获取是初始化”,是处理异常而不是使用显式try / catch块的更清晰的方法。使用RAII,显式的try / catch块主要仅用于最终处理异常,例如,以便向用户显示错误消息。查看上面的代码,在我看来,输入foo()
可以被视为有责任将任何异常报告为std::runtime_error("foo failed")
并将详细信息嵌套在嵌套异常中。如果我们可以使用RAII来获得这个责任,那么代码看起来会更清晰:
void foo() {
Throw_with_nested on_error("foo failed");
// code that might throw
std::ifstream file("nonexistent.file");
file.exceptions(std::ios_base::failbit);
}
有没有办法在这里使用RAII语法来替换显式的try / catch块?
要做到这一点,我们需要一种类型,当它的析构函数被调用时,检查析构函数调用是否是由异常引起的,如果是,则嵌套该异常,并抛出新的嵌套异常,以便正常继续展开。这可能看起来像:
struct Throw_with_nested {
const char *msg;
Throw_with_nested(const char *error_message) : msg(error_message) {}
~Throw_with_nested() {
if (std::uncaught_exception()) {
std::throw_with_nested(std::runtime_error(msg));
}
}
};
但是std::throw_with_nested()
要求“当前处理的异常”处于活动状态,这意味着除了catch块的上下文之外它不起作用。所以我们需要这样的东西:
~Throw_with_nested() {
if (std::uncaught_exception()) {
try {
rethrow_uncaught_exception();
}
catch(...) {
std::throw_with_nested(std::runtime_error(msg));
}
}
}
不幸的是,据我所知,C ++中没有定义rethrow_uncaught_excpetion()
。
答案 0 :(得分:3)
如果没有在析构函数中捕获(并使用)未捕获的异常的方法,则在没有调用std::terminate
的析构函数的上下文中,无法重新抛出嵌套或不嵌套的异常(在异常处理的上下文中抛出异常时。)
std::current_exception
(与std::rethrow_exception
结合使用)只返回指向当前处理的异常的指针。这排除了在这种情况下的使用,因为在这种情况下的例外是明确未处理的。
鉴于上述情况,给出的唯一答案是从美学的角度来看。功能级别的try块使这个看起来稍微不那么难看。 (根据你的风格偏好调整):
void foo() try {
// code that might throw
std::ifstream file("nonexistent.file");
file.exceptions(std::ios_base::failbit);
}
catch(...) {
std::throw_with_nested(std::runtime_error("foo failed"));
}
答案 1 :(得分:3)
考虑简单的规则
破坏者绝不能扔掉。
RAII无法实现您想要的东西。该规则有一个简单的原因:如果析构函数由于飞行中的异常而在堆栈展开期间抛出异常,则调用terminate()
并且您的应用程序将死亡。
在C ++ 11中,你可以使用lambdas,它可以让生活更轻松。你可以写
void foo()
{
giveErrorContextOnFailure( "foo failed", [&]
{
// code that might throw
std::ifstream file("nonexistent.file");
file.exceptions(std::ios_base::failbit);
} );
}
如果您通过以下方式实现函数giveErrorContextOnFailure
:
template <typename F>
auto giveErrorContextOnFailure( const char * msg, F && f ) -> decltype(f())
{
try { return f(); }
catch { std::throw_with_nested(std::runtime_error(msg)); }
}
这有几个好处:
try
,catch
,std::throw_with_nested
和std::runtime_error
。这使您的代码更易于维护。如果您想更改程序的行为,则只需在一个地方更改代码。foo()
应该返回某些内容,那么您只需在函数foo()中return
之前添加giveErrorContextOnFailure
。在发布模式下,与try-catch-way相比,通常没有性能面板,因为模板默认是内联的。
请勿使用
std::uncaught_exception()
。
Herb Sutter有一个很好的article about this话题,完美地解释了这条规则。简而言之:如果你有一个函数f()
在堆栈展开期间从析构函数中调用
void f()
{
RAII r;
bla();
}
RAII
的析构函数看起来像
RAII::~RAII()
{
if ( std::uncaught_exception() )
{
// ...
}
else
{
// ...
}
}
然后将始终使用析构函数中的第一个分支,因为在外部析构函数中,堆栈展开std::uncaught_exception()
将始终返回true,即使在包含析构函数RAII
的析构函数中调用的函数内也是如此。