繁体   English   中英

如果没有uncaught_exception,则在C ++析构函数中有例外

[英]Exceptions in C++ destructors if there is no uncaught_exception

人们强烈反对从析构函数中抛出异常。 以此答案为例。 我想知道是否可以使用std::uncaught_exception()来可移植地检测我们是否正在处理由于某些其他异常而展开堆栈的过程。

我发现自己故意在析构函数中抛出异常。 提到两个可能的用例:

  • 一些资源清理涉及刷新缓冲区,因此失败可能表示截断输出。
  • 破坏持有std::exception_ptr的对象,该对象可能包含在不同线程中遇到的异常。

简单地忽略这些特殊情况是完全错误的。 并且有可能通过抛出异常,一些异常处理程序可能能够提供比析构函数本身写入std::cerr更有用的上下文信息。 此外,为所有失败的断言抛出异常是我的单元测试方法的重要部分。 在这种情况下,错误消息后跟忽略的错误条件将不起作用。

所以我的问题是, 除了正在处理另一个异常之外是否可以抛出异常 ,或者是否有理由不这样做?

把它放在代码中:

Foo::~Foo() {
  bool success = trySomeCleanupOperation();
  if (!success) {
    if (std::uncaught_exception())
      std::cerr << "Error in destructor: " << errorCode << std::endl;
    else
      throw FooOperationFailed("Error in destructor", errorCode);
  }
}

据我所知,这应该是安全的,并且在许多情况下比不抛出异常更好。 但我想验证一下。

Herb Sutter写了这个主题: http//www.gotw.ca/gotw/047.htm

他的结论是永远不会从析构函数中抛出,总是使用在不能抛出的情况下使用的机制报告错误。

原因有两个:

  • 它并不总是有效。 有时uncaught_exception返回true,但抛出是安全的。
  • 以两种不同的方式报告相同的错误是不好的设计,如果他们想知道错误,用户将不得不考虑这两种方式。

请注意,对于任何给定的可重用代码段,无法确定在堆栈展开期间永远不会调用它。 无论您的代码是什么,您都无法确定它的某些用户不希望从具有try/catch的析构函数中调用它来处理其异常。 因此,如果可以安全抛出,则不能依赖uncaught_exception总是返回true,除非通过记录函数,“不能从析构函数中调用”。 如果你使用它,那么所有的调用者也必须记录他们的功能,“不能从析构函数调用”,你有一个更烦人的限制。

除了其他任何东西,nothrow保证对用户很有价值 - 如果他们知道他们所做的特定事情不会抛出,它可以帮助他们编写异常安全的代码。

一种方法是给你的类一个成员函数close ,调用trySomeCleanupOperation并在它失败时抛出。 然后析构函数调用trySomeCleanupOperation并记录或抑制错误,但不抛出。 如果用户想要知道他们的操作是否成功,那么用户可以调用close ,如果他们不关心,只需让析构函数处理它(包括析构函数作为堆栈展开的一部分被调用的情况,因为抛出异常在达到用户的呼叫close )。 “啊哈!”,你说,“但是这违背了RAII的目的,因为用户必须记住调用close !”。 是的,有点,但问题不在于RAII是否可以做你想做的一切。 它不能。 问题在于它是否始终比你想要的少(你希望它在trySomeCleanupOperator失败时抛出异常),或者在堆栈展开期间使用时不那么令人惊讶

此外,为所有失败的断言抛出异常是我的单元测试方法的重要部分

这可能是一个错误 - 您的单元测试框架应该能够将terminate()视为测试失败。 假设一个断言在堆栈展开期间失败 - 当然你想记录它,但你不能通过抛出异常来做到这一点,所以你已经把自己画成了一个角落。 如果您的断言终止,那么您可以将它们检测为终止。

不幸的是,如果你终止,那么你不能运行其余的测试(至少,不是在那个过程中)。 但是如果一个断言失败,那么一般来说你的程序处于未知且可能不安全的状态。 因此,一旦你的断言失败了,你无论如何都不能依赖于在该过程中做任何其他事情。 您可以考虑将测试框架设计为使用多个进程,或者只是接受足够严重的测试失败将阻止其余测试运行。 在测试框架的外部,您可以认为您的测试运行有三个可能的结果“全部通过,一些失败,测试崩溃”。 如果测试运行未能完成,那么您不会将其视为通过。

关于dtors和例外,这就是标准所说的:

15.2

(......)

在从try块到抛出异常的路径上构造的自动对象上调用析构函数的过程称为“堆栈展开”。如果在堆栈展开期间调用的析构函数以异常退出,则调用std :: terminate( 15.5.1)。 [ 注意:因此析构函数通常应该捕获异常,而不是让它们从析构函数中传播出来。 - 尾注 ]

既然你问了很模糊的问题,答案取决于:

  • 你可以在dtor中抛出一个异常,这样应用程序不会崩溃吗? 是的 ,你可以(在术语中:它将编译,有时它将正确运行)。
  • 应该在dtor中抛出异常吗? ,你不应该,因为它可能(通常它会)导致你的问题。

我要说,需要从dtor抛出异常是设计糟糕的表现。 看来,你在析构函数中做了一些事情,应该在其他地方完成。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM