我了解到,如果在堆栈展开期间发生析构函数抛出异常会中止,因为这样会传播多个异常。
以下是带有注释的示例,可以证明这一点:
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;
}
问题是,这是处理内部构造异常的正常方法吗?有没有可能使该设计出错的示例?
主要目标是拥有异常安全的析构函数。
还有一个附带的问题,在第二个示例中,在堆栈展开期间,仍然有2个异常在传播,怎么不调用abort?堆栈展开期间是否只允许一个异常?
答案 0 :(得分:1)
~Foo() noexcept
在这种情况下,noexcept
是多余的,因为没有子对象具有可能抛出的析构函数。析构函数将隐式noexcept
而不使用
问题是,这是处理残渣内部异常的正常方法吗?
Try-catch通常是处理异常的方式,无论是在析构函数内部还是其他方式。
但是,在这种情况下,更好的解决方案是:
void ReleaseResources()
{
delete pInt;
}
没有必要扔在这里,不这样做会更简单。
还有一个附带的问题,在第二个示例中,在堆栈展开期间,仍然有2个异常在传播,怎么不调用abort?
因为允许。
答案 1 :(得分:1)
问题是,这是处理内部构造异常的正常方法吗?有没有可能使该设计出错的示例?
是的,如果您的// handle exception here
代码实际上处理了异常,则可以避免抛出这样的析构函数,例如 。但是实际上,如果在销毁过程中引发异常,则通常意味着没有好的方法来处理该异常。
从析构函数中抛出意味着某种清理失败。也许资源泄漏,数据无法保存,现在丢失或某些内部状态无法设置或还原。不管是什么原因,只要您可以避免或解决问题,都不必首先考虑。
只有当您实际上没有遇到这种情况时,您对这种情况(抛出析构函数)的解决方案才有效。在实践中,如果您尝试应用此功能,除了可能警告用户或记录问题之外,您将发现没有任何写// handle exception here
的内容。
在堆栈展开期间是否只允许一个异常?
没有这样的规则。堆栈展开期间抛出的问题是未捕获的异常是否从析构函数中逸出。如果析构函数在内部引发并捕获异常,则它对正在进行的堆栈展开没有任何影响。 std::terminate
明确说明堆栈退绕何时终止(link):
在某些情况下,必须放弃异常处理,以减少不太细微的错误处理技术。这些情况是:
[...]
-当堆栈展开期间对象的销毁因引发异常而终止时,或者
[...]