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

时间:2019-10-28 18:27:55

标签: c++ exception destructor

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

以下是带有注释的示例,可以证明这一点:

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?堆栈展开期间是否只允许一个异常?

2 个答案:

答案 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):

  

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

     

[...]

     

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

     

[...]