C++使用RAII和抛出的析构函数
C++ using RAII with destructor that throws
假设我有RAII类:
class Raii {
Raii() {};
~Raii() {
if (<something>) throw std::exception();
}
};
如果我有这个功能:
void foo() {
Raii raii;
if (something) {
throw std::exception();
}
}
这很糟糕,因为在清理第一个异常时,我们可以再次抛出,这将终止进程。
我的问题是,对于清理可能抛出的代码,使用raii的好模式是什么?
例如,这是好的还是坏的——为什么?
class Raii {
Raii() {};
~Raii() {
try {
if (<something>) throw std::exception();
}
catch (...) {
if (!std::uncaught_exception())
throw;
}
}
};
请注意,Raii对象始终是堆栈分配的对象,这不是一般的从析构函数抛出的问题。
C++几乎肯定会有一个函数来获取截至C++1z的当前异常计数(如果他们按时发布,也就是C++17!):std::uncaught_exceptions
(注意复数"s")。此外,析构函数在默认情况下声明为noexcept
(这意味着如果您试图通过异常退出析构函数,则会调用std::terminate
)。
因此,首先,将析构函数标记为抛出(noexcept(false)
)。接下来,跟踪ctor中活动异常的数量,将其与dtor中的值进行比较:如果dtor中有更多未捕获的异常,则您知道当前正在进行堆栈展开,再次抛出将导致对std::terminate
的调用。
现在你决定了你到底有多特别,以及你希望如何处理这种情况:终止程序,还是只是吞下内部的异常?
一个糟糕的模仿是,如果uncaught_exception
(单数)返回true,则不抛出,但这会使异常在从试图捕获和处理您的异常的展开触发的不同dtor调用时不起作用。此选项在当前C++标准中可用。
ScopeGuard文章中的建议是
在异常领域中,如果您的"撤消/恢复"操作失败,则根本无法执行任何操作。您尝试执行撤消操作,然后继续执行,不管撤消操作是否成功。
这听起来可能很疯狂,但考虑一下:
- 我设法耗尽了内存,得到了一个
std::bad_alloc
异常 - 我的清理代码记录了错误
- 不幸的是,写入失败(可能磁盘已满),并试图引发异常
我可以撤消日志写入吗?我应该试试吗?
当抛出异常时,您真正知道的只是程序处于无效状态。有些不可能的事情最终变成了可能,你不应该感到惊讶。就我个人而言,我看到的Alexandrescu的建议比其他情况更有意义的情况要多得多:尝试清理,但要认识到第一个异常意味着事情已经处于无效状态,所以额外的故障——尤其是由第一个问题("错误级联")引起的故障——应该不会令人惊讶。尝试处理它们不会有好的结果。
我可能应该提到,Proto船长正是按照你的建议行事:
当Cap‘n Proto代码可能从析构函数抛出异常时,它首先检查
std::uncaught_exception()
以确保这是安全的。如果另一个异常已经处于活动状态,则新异常被认为是主异常的副作用,并且被静默地吞噬或在侧通道上报告。
但是,正如Yakk所说,在C++11中,析构函数默认为nothrow(true)
。这意味着,如果你想这样做,你需要确保在C++11和更高版本中,你将析构函数标记为nothrow(false)
。否则,即使在运行中没有其他异常,从析构函数抛出异常也会终止程序。请注意,"如果另一个异常已经处于活动状态,则新异常被认为是主异常的副作用,并且被静默地吞噬或在侧通道上报告。"
- 使用基类指针创建对象时,缺少派生类析构函数
- 在c++中使用向量时,如何调用构造函数和析构函数
- 我需要知道编译器如何在cpp中使用析构函数
- 为什么在使用转换构造函数赋值后调用C++类的析构函数?
- C++:使用方法调用析构函数的顺序是什么?
- 为什么使用析构函数会使类不可复制?
- 是否可以使用函数指针调用虚拟析构函数?
- C++类析构函数使用新值而不是实际值
- 如果我也使用复制构造函数并且重载 = 运算符,我是否需要析构函数?
- 使用析构函数释放链表
- 为什么添加析构函数(甚至是空的)会破坏我的结构,该结构使用 ref 转发和折叠来保存 ref 或值的副本?
- 如何从类成员函数返回指针,例如 size_t * class :: function(); 并使用类析构函数 ~size
- C++使用函数对象的线程,如何调用多个析构函数而不是构造函数?
- 即使基类和派生类只使用基元数据类型,我是否需要定义虚拟析构函数
- 是否可以在其析构函数中使用指向已销毁对象的指针?
- 使用私有析构函数删除动态分配的对象
- 在从仅移动类型派生的类中定义析构函数在使用 std::vector emplace_back或push_back创建时会
- LNK2019构造函数/析构函数使用 C++ Dll
- C++析构函数使用动态内存泄漏
- 永远不要在 PIMPL 中提供析构函数(使用 boost scoped_ptr),g++(4.6.1) 不会生成编译错误,为什么?