代码之家  ›  专栏  ›  技术社区  ›  metablaster

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

  •  1
  • metablaster  · 技术社区  · 6 年前

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

    下面是一个带有注释的示例,说明了这一点:

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

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

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

    另一个附带问题是,在第二个示例中,在堆栈展开期间仍有两个异常正在传播,如何调用不中止?堆栈展开期间是否只允许一个异常?

    0 回复  |  直到 6 年前
        1
  •  1
  •   François Andrieux    6 年前

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

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

    从析构函数中抛出意味着某种清理失败。可能是资源泄漏、数据无法保存、现在丢失或无法设置或还原某些内部状态。不管是什么原因,如果你能避免或解决这个问题,你就不必把它放在首位。

    你对这种坏情况的解决方案(抛出析构函数)只有在你没有真正处于坏情况下才有效。实际上,如果你试着应用这个,你会发现没有什么东西可以写 //在此处处理异常 ,但可能警告用户或记录问题。


    堆栈展开期间是否只允许一个异常?

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

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

    [...]

    --当堆栈展开过程中对象的销毁因引发异常而终止时,或

    [...]

        2
  •  1
  •   eerorika    6 年前
    ~Foo() noexcept
    

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

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

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

    不过,在这种情况下,更好的解决办法是:

    void ReleaseResources()
    {
        delete pInt;
    }
    

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

    另一个附带问题是,在第二个示例中,在堆栈展开期间仍有两个异常正在传播,如何调用不中止?

    因为这是允许的。

    推荐文章