使用RAII嵌套异常
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是不可能的
考虑的简单规则
析构函数决不能抛出
RAII不可能实现您想要的东西。该规则有一个简单的原因:如果析构函数在堆栈展开过程中由于飞行中的异常而抛出异常,则调用terminate()
,您的应用程序将失效。
另一种选择
在C++11中,你可以使用Lambda,这可以让生活变得更轻松。你可以写
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)); }
}
这有几个优点:
- 您封装了错误的嵌套方式
- 如果在整个程序范围内严格遵循此技术,则可以对整个程序更改嵌套错误的方式
- 错误消息可以像RAII中那样写在代码之前。这种技术也可以用于嵌套作用域
- 代码重复较少:不必编写
try
、catch
、std::throw_with_nested
和std::runtime_error
。这使代码更易于维护。如果你想改变程序的行为,你只需要在一个地方改变你的代码 - 返回类型将自动推导出来。因此,如果函数
foo()
应该返回一些东西,那么只需在函数foo()中的giveErrorContextOnFailure
之前添加return
在发布模式中,与try-catch方式相比,通常不会有性能面板,因为默认情况下模板是内联的。
还有一条有趣的规则:
请勿使用
std::uncaught_exception()
赫伯·萨特有一篇关于这个话题的好文章,完美地解释了这个规则。简而言之:如果你有一个函数f()
,它在堆栈展开过程中从析构函数内被调用,看起来像这个
void f()
{
RAII r;
bla();
}
其中RAII
的析构函数看起来像
RAII::~RAII()
{
if ( std::uncaught_exception() )
{
// ...
}
else
{
// ...
}
}
则析构函数中的第一个分支将始终被采用,因为在堆栈展开期间的外部析构函数std::uncaught_exception()
将始终返回true,即使在从该析构函数调用的函数内部,包括RAII
的析构函数。
- 嵌套在类中时无法设置成员数据
- 无法访问嵌套类.类的使用无效
- 我正在使用嵌套的while循环来解析具有多行的文本文件,但由于某种原因,它只通过第一行,我不知道为什么
- 如何在C++中初始化嵌套类中的2个memeber
- 如何声明特征矩阵,然后通过嵌套循环初始化它
- 在C++中搜索嵌套多映射值
- 在C++中将矢量转换为嵌套地图
- C++嵌套if语句,基本货币交换
- 在nlohmann json中,如何将嵌套对象的数组转换为嵌套结构的向量
- 嵌套的匿名命名空间
- 了解嵌套循环打印星号图案
- 如何使用boost::具有嵌套结构和最小代码更改的序列化
- 嵌套for循环C++的问题(初学者)
- 从嵌套在std::映射中的std::列表中删除元素的最佳方式
- 嵌套异常和基元类型
- 使用RAII嵌套异常
- 正在从具有嵌套异常的catch块重新引发异常
- c++异常处理程序中嵌套的try..catch
- 受嵌套处理程序影响的异常的生命周期
- 三元操作符:抛出异常和嵌套