在析构函数内部处理异常(但不抛出)

Handling exceptions inside destructor (but not throwing out)

本文关键字:析构函数 内部 处理 异常      更新时间:2023-10-16

我已经了解到,如果抛出析构函数程序,如果在堆栈展开期间发生这种情况,则会中止,因为这样会传播多个异常。

下面是一个示例,并附有说明:

class Foo
{
public:
~Foo()
{
ReleaseResources();
}
private:
int* pInt;
void ReleaseResources()
{
if (!pInt)
throw 0;
else delete pInt;
}
};
int main() try
{
{
Foo local;
throw 1;
} // aborting here, because now 2 exceptions are propagating!
return 0;
}
catch (int& ex)
{
return ex;
}

然而,我有一个类层次结构,其中一个析构函数调用了一个可能抛出的函数,并且由于该入口层次结构被破坏,这意味着现在所有析构函数都被标记为noexcept(false)

虽然编译器插入异常代码是可以的,但对于这些类的用户来说是不可以的,因为如果发生上述代码示例中的场景,它不会阻止中止程序。

因为我希望析构函数是异常安全的,所以我想把它们都标记为noexcept,但在析构函数内部处理可能的异常,如下所示:

相同的样本,但经过重新处理,无法中止,析构函数异常安全:

class Foo
{
public:
~Foo() noexcept
{
try
{
ReleaseResources();
}
catch (int&)
{
// handle exception here
return;
}
}
private:
int* pInt;
void ReleaseResources()
{
if (!pInt)
throw 0;
else delete pInt;
}
};
int main() try
{
{
Foo local;
throw 1;
} // OK, not aborting here...
return 0;
}
catch (int& ex)
{
return ex;
}

问题是,这种处理destrucotr内部异常的正常方法吗?有什么例子会使这个设计出错吗?

主要目标是拥有异常安全的析构函数。

还有一个附带问题,在第二个示例中,在堆栈展开期间,仍有2个异常在传播,如何不调用中止?如果在堆栈展开过程中只允许一个异常?

问题是,这是处理destrucotrs内部异常的正常方法吗?有什么例子会使这个设计出错吗?

是的,如果您的// handle exception here代码确实处理了异常,您可以避免抛出类似这样的析构函数。但在实践中,如果在销毁过程中抛出异常,通常意味着没有好的方法来处理该异常。

从析构函数抛出意味着某种清理失败。可能是资源泄漏,数据无法保存,现在丢失,或者某些内部状态无法设置或恢复。无论是什么原因,如果你能避免或解决问题,你一开始就不必抛出。

对于这种糟糕的情况(抛出析构函数(,只有当你实际上没有处于糟糕的情况时,你的解决方案才有效。在实践中,如果您尝试应用这一点,您会发现除了警告用户或记录问题之外,没有任何内容可以编写// handle exception here


如果在堆栈展开期间只允许一个异常?

没有这样的规则。堆栈展开期间抛出的问题是,如果未捕获的异常从析构函数中逃逸。如果析构函数在内部抛出并捕获异常,则对正在进行的堆栈解算没有影响。std::terminate明确表示堆栈展开在终止(链接(时结束:

在某些情况下,必须放弃异常处理,以采用不那么微妙的错误处理技术。这些情况是:

[…]

--当堆栈展开过程中对象的销毁通过抛出异常或而终止时

[…]

~Foo() noexcept

noexcept在这种情况下是多余的,因为没有可能抛出析构函数的子对象。如果没有,析构函数将是隐式noexcept

问题是,这是处理destrucotr内部异常的正常方法吗?

Try-catch通常是处理异常的方式,无论是在析构函数内部还是其他方面。

然而,在这种特殊情况下,更好的解决方案是:

void ReleaseResources()
{
delete pInt;
}

这里没有必要扔,不扔会更简单。

还有一个附带问题,在第二个例子中,在堆栈展开期间,仍有2个异常在传播,如何不调用中止?

因为这是允许的。