【问题标题】:Handling exceptions inside destructor (but not throwing out)处理析构函数内的异常(但不抛出)
【发布时间】:2020-02-24 01:07:36
【问题描述】:

我了解到,如果在堆栈展开期间发生这种情况,如果抛出析构函数程序将中止,因为会传播超过 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;
}

但是我有一个类层次结构,其中一个析构函数调用一个可能抛出的函数,并且由于该条目层次结构被污染,这意味着现在所有析构函数都标记为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 个异常在传播,如何调用不中止?如果在堆栈展开期间只允许一个异常?

【问题讨论】:

    标签: c++ exception destructor


    【解决方案1】:

    问题是,这是处理析构函数内部异常的正常方法吗?有什么例子可以让这个设计出错吗?

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

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

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


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

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

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

    [...]

    -- 当堆栈展开期间对象的销毁因抛出异常而终止时,或

    [...]

    【讨论】:

    • 我应该提到在析构函数中调用的潜在抛出函数是来自 3rd 方库的函数,例如来自 COMRelease() 我无法控制该函数,我只知道它可能会抛出。所以如果这个函数在程序不应该干净地关闭但继续运行时抛出,那么可能会记录问题并手动中止是要走的路吗?
    • @metablaster 记录和终止可能是您唯一合理的做法。彻底关闭可能没问题,但取决于您首先遇到异常的原因,该异常也可能不会成功。例如,如果关闭依赖于该库的其他功能,它们可能也不起作用。
    【解决方案2】:
    ~Foo() noexcept
    

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

    的析构函数将隐式为 noexcept

    问题是,这是处理析构函数内部异常的正常方法吗?

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

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

    void ReleaseResources()
    {
        delete pInt;
    }
    

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

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

    因为这是允许的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-26
      • 1970-01-01
      • 2014-08-12
      • 2022-01-16
      • 1970-01-01
      • 2021-05-27
      相关资源
      最近更新 更多