所以我的问题是-如果从析构函数中抛出导致未定义的行为,那么如何处理析构函数期间发生的错误?
主要问题是:您不能失败。失败失败到底意味着什么?如果将事务提交到数据库失败,并且失败(无法回滚),那么我们的数据完整性会怎样?
由于析构函数同时针对正常和异常(失败)路径进行调用,因此它们本身不会失败,否则我们将“失败”。
这是一个概念上很困难的问题,但通常的解决方案是找到一种确保失败不会失败的方法。例如,数据库可能会在提交到外部数据结构或文件之前写入更改。如果事务失败,则可以丢弃文件/数据结构。然后,只需确保从该外部结构/文件提交更改就不会失败。
务实的解决方案也许只是确保从天文学角度讲不可能发生失败的可能性,因为在某些情况下使不可能失败的事情几乎是不可能的。
对我来说,最合适的解决方案是以一种不会导致清理逻辑失败的方式编写您的非清理逻辑。例如,如果您想创建一个新的数据结构来清理现有的数据结构,那么您可能会事先寻求创建该辅助结构,以使我们不再需要在析构函数中创建它。
坦率地说,这说起来容易做起来难,但这是我看到的唯一正确的方法。有时,我认为应该为正常的执行路径写一些独立的析构函数逻辑,而不必使用特殊的析构函数,因为有时候析构函数会感觉有点像它们通过尝试同时处理这两者而承担着双重责任(例如,范围保护需要显式解雇;如果他们可以区分特殊的销毁路径和非例外的销毁路径,则不需要这样做。
最终的问题仍然是我们不能失败,这是一个很难在所有情况下完美解决的概念设计难题。如果您不必太复杂地控制复杂的控制结构,而使大量的小对象彼此交互,而以稍微大一些的方式对设计进行建模(例如:带有析构函数的粒子系统来破坏整个粒子),它会变得更加容易。系统,而不是每个粒子单独的非平凡的析构函数)。当您在这种较粗糙的级别上对设计进行建模时,您需要处理的琐碎析构函数将更少,并且通常还可以提供所需的任何内存/处理开销来确保析构函数不会失败。
这自然是最简单的解决方案之一,就是减少析构函数的使用频率。在上面的粒子示例中,也许在销毁/移除粒子后,应该执行某些可能由于任何原因而失败的操作。在那种情况下,您可以通过粒子系统在删除粒子时通过粒子系统来完成所有这些工作,而不必通过可以在特殊路径中执行的逻辑来调用此类逻辑。移除粒子可能总是在非异常路径中完成。如果系统被破坏,也许它只能清除所有粒子,而不会打扰可能失败的单个粒子清除逻辑,而可能失败的逻辑仅在粒子系统清除一个或多个粒子时的正常执行期间执行。
如果您避免使用非平凡的析构函数处理许多小对象,通常会有类似的解决方案出现。当您纠结在很多都带有非平凡的dtory对象的混乱对象中时,似乎几乎不可能出现异常安全的情况就可能使您陷入混乱。
如果将nothrow / noexcept实际翻译为编译器错误(如果指定该错误的任何内容)(包括应继承其基类的noexcept规范的虚函数)试图调用可能抛出的任何内容,则将大有帮助。这样,如果我们实际上无意中编写了一个可能抛出的析构函数,那么我们将能够在编译时捕获所有这些东西。