我知道我不应该从析构函数中抛出异常。
如果我的析构函数调用了一个可以抛出异常的函数,如果我在析构函数中捕获它并且不进一步抛出它可以吗?还是它会导致中止,我根本不应该从析构函数中调用这些函数?
是的,这是合法的。异常不能从析构函数中逃脱,但是析构函数内部或它调用的函数中发生的任何事情都取决于您。
(从技术上讲,异常也可以从析构函数调用中逃脱。如果由于抛出另一个异常而在堆栈展开期间发生这种情况,std::terminate
则会调用它。因此它在标准中得到了很好的定义,但这是一个非常糟糕的主意。)
是的。
以标准库中的 std::fstream 类为例。
这个概念是,如果析构函数调用任何可以抛出的方法,那么这些方法应该是公共的。因此,如果您的对象的用户想要检查异常,他们可以使用公共方法并处理异常。如果他们不关心异常,那么就让析构函数处理问题。
回到 std::fstream 示例。
{
std::fstream text("Plop");
// Load Text.
// I don't care if the close fails.
// So let the destructor handle it and discard exceptions
}
{
// If this fails to write I should at least warn the user.
// So in this case I will explicitly try and close it.
try
{
std::ofstram password("/etc/password");
// Update the password file.
password.close();
}
catch(...)
{
Message.ShowDialog("You failed to update the Password File");
}
}
如果一个异常在另一个正在传播的异常的堆栈展开期间离开析构函数,则调用 std::terminate()。
当没有堆栈展开正在进行时,异常可能会在没有调用 std::terminate() 的情况下离开析构函数。但是,对于在堆上分配的对象,这将导致内存泄漏,因为不会为从其析构函数中抛出异常的对象调用“操作员删除”。令人惊讶的是,在这种情况下,基类的析构函数仍然被调用:如果派生类析构函数抛出异常,基类析构函数会发生什么
如果异常在析构函数中被捕获(这样异常不会离开析构函数),那么即使另一个异常的堆栈展开正在进行中也没有问题。这种情况在这里有更深入的描述:http: //bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI11_FR.HTM
简单的答案,绝不允许来自 dtor 的异常!
复杂的答案。只有当另一个异常处于活动状态时异常从 dtor 中逃脱,你才会真正被钉牢。正常情况是当您已经从另一个异常中展开堆栈并且相关对象被销毁时。在这种情况下,如果异常逃脱了 dtor 然后被调用,请注意您可以通过调用std::terminate
放入自己的处理程序。的默认实现是调用 abort。std::terminate
std::set_terminate
std::terminate
更复杂的是,大多数想要对其异常安全性做出任何保证的函数,主要是基本保证或强保证,都依赖于底层类型而不是抛出它们的 dtor*
真正的问题是,当这个错误发生时你的程序会处于什么状态?你怎么能康复?这种恢复应该在哪里处理?您需要查看您的具体案例并解决这些问题。有时捕获异常并忽略它就可以了。其他时候你需要提出一些危险信号。
所以答案是:C++ 允许它在 dtor 中抛出异常,但你永远不应该让它逃脱。
- 回顾:简要定义 Abrahams 异常安全保证(basic、strong 和 nothrow)。
基本保证是失败的操作可能会改变程序状态,但不会发生泄漏,并且受影响的对象/模块仍然是可破坏和可用的,处于一致(但不一定是可预测的)状态。
强保证涉及事务提交/回滚语义:失败的操作保证程序状态相对于操作的对象保持不变。这意味着没有影响对象的副作用,包括相关帮助对象的有效性或内容,例如指向被操作容器的迭代器。
nothrow 保证意味着不会发生失败的操作。该操作不会引发异常。
您可能会发现C++ FAQ Lite 中的此页面提供了丰富的信息。基本答案是“不要这样做”。您计划在哪里捕获此异常?无论您在捕获该异常时打算做什么,只需使用函数调用或其他方式(例如记录它或设置一个标志以警告用户或其他方式)即可。从析构函数中抛出异常可能会导致整个程序终止。