[英]Using RAII to nest exceptions
因此,使用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块,希望嵌套异常,这至少可以说是丑陋的。
Jon Kalb扩展为“责任获取是初始化”的RAII,是处理异常而不是使用显式try / catch块的更清晰的方法。 使用RAII,显式try / catch块主要仅用于最终处理异常,例如,为了向用户显示错误消息。
查看上面的代码,在我看来,输入foo()
可以被视为有责任将任何异常报告为std::runtime_error("foo failed")
并将详细信息嵌套在nested_exception中。 如果我们可以使用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()
。
在缺少捕获(并消耗)析构函数中未捕获的异常的方法的情况下,没有方法在析构函数的上下文中重新抛出异常(嵌套或不嵌套),而不调用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"));
}
考虑到简单的规则
破坏者绝不能扔掉。
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()中的giveErrorContextOnFailure
之前添加return
。 在发布模式下,与try-catch-way相比,通常没有性能面板,因为模板默认是内联的。
不要使用
std::uncaught_exception()
。
Herb Sutter有一篇关于这个主题的好文章 ,完美地解释了这个规则。 简而言之:如果你有一个函数f()
,它在堆栈展开期间从析构函数中调用,就像这样
void f()
{
RAII r;
bla();
}
RAII
的析RAII
看起来像
RAII::~RAII()
{
if ( std::uncaught_exception() )
{
// ...
}
else
{
// ...
}
}
那么析构函数中的第一个分支将始终被占用,因为在堆栈展开期间,在外部析构函数中, std::uncaught_exception()
将始终返回true,即使在包含RAII
函数的RAII
函数调用的函数内也是如此。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.