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

Handling exceptions inside destructor (but not throwing out)

我了解到,如果在堆栈展开期间抛出析构函数程序将会中止,因为这样会传播超过 1 个异常。

这是一个带有注释的示例:

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;
}

但是我有一个 class 层次结构,其中一个析构函数调用一个可能抛出的函数,并且由于该条目层次结构被毒化,这意味着现在所有析构函数都被标记为 noexcept(false).

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

因为我希望析构函数是异常安全的,所以我想到将它们全部标记为 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;
}

问题是,这是在 destrucotrs 中处理异常的正常方法吗?有什么例子可以使这个设计出错吗?

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

还有一个附带问题,在第二个例子中,在堆栈展开期间仍然有 2 个异常传播,为什么没有中止被调用?如果堆栈展开期间只允许一个异常?

~Foo() noexcept

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

The question is, is this normal approach to handle exceptions inside destrucotrs?

Try-catch 通常是在析构函数内部或其他地方处理异常的方式。

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

void ReleaseResources()
{
    delete pInt;
}

这里不用扔,不扔会更简单

Also a side question, in this second example, during stack unwinding there are still 2 exceptions propagating, how is that no abort is called?

因为这是允许的。

The question is, is this normal approach to handle exceptions inside destrucotrs? are there any examples that could make this design go wrong?

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

从析构函数中抛出意味着某种清理失败。也许资源泄露了,数据无法保存并且现在丢失了,或者某些内部状态无法设置或恢复。无论是什么原因,如果您可以避免或解决问题,您就不必首先抛出问题。

你对这种糟糕情况的解决方案(抛出析构函数)只有在你实际上没有处于糟糕情况时才有效。实际上,如果您尝试应用它,您会发现没有什么可写的 // handle exception here,除了可能警告用户或记录问题。


if only one exception is allowed during stack unwinding?

没有这样的规则。在堆栈展开期间抛出的问题是如果未捕获的异常从析构函数中逃逸。如果析构函数在内部抛出并捕获异常,则它不会影响正在进行的堆栈展开。 std::terminate 明确说明堆栈展开何时终止 (link) :

In some situations exception handling must be abandoned for less subtle error handling techniques. These situations are:

[...]

-- when the destruction of an object during stack unwinding terminates by throwing an exception, or

[...]