为什么在析构函数中抛出异常时不会调用重载删除?

时间:2017-08-12 20:52:27

标签: c++ c++11 exception destructor delete-operator

我编写了下面的代码,它重载newdelete运算符,并在析构函数中抛出异常。

抛出异常时,为什么delete运算符中的代码未执行(并且“再见”打印)?

如果不应该执行,(如何)释放内存?是one of the other delete operators吗?是否会重载其中一个而导致执行相应的代码?或者内存根本没有释放,因为破坏失败意味着它可能不应该是?

#include <iostream>
using namespace std;
class A
{
public:
    A() { }
    ~A() noexcept(false) { throw exception(); }
    void* operator new (std::size_t count)
    {
        cout << "hi" << endl;
        return ::operator new(count);
    }
    void operator delete (void* ptr)
    {
        cout << "bye" << endl;
        return ::operator delete(ptr);
    }
    // using these (with corresponding new's) don't seem to work either
    // void operator delete (void* ptr, const std::nothrow_t& tag);
    // void operator delete (void* ptr, void* place);
};

int main()
{
    A* a = new A();
    try
    {
        delete a;
    }
    catch(...)
    {
        cout << "eek" << endl;
    }
    return 0;
}

输出:

hi
eek

Live demo

我看了看:

但是我无法找到解析器(与构造函数相对)中的异常(1)以及(2)带有重载删除的确切发生的答案。

我不需要在析构函数中抛出一个异常的演讲 - 我只是遇到了类似的代码,我对这种行为感到好奇。

如果存在这样的引用,我希望得到标准或类似引用支持的答案。

3 个答案:

答案 0 :(得分:6)

standard Draft N4296 5.3.5,第121页说:

  

[expr.delete] [注意:释放功能   无论对象的析构函数或数组的某个元素是否抛出异常,都会调用它。    - 结束说明]

因此,必须在析构函数抛出的情况下调用operator delete

但是,从评论中可以看出,某些编译器没有正确调用operator delete。这可以作为bug编译器来解决。

错误测试:

答案 1 :(得分:2)

在1998 C ++标准(ISO / IEC 14882第一版,1998-09-01)中,第6和第7段的“第5.3.5节删除[expr.delete]”中详细说明了删除表达式的工作原理。

  

6 delete-expression 将为对象或要删除的数组元素调用析构函数(如果有)。在数组的情况下,元素将按地址递减的顺序销毁(即,在   完成构造函数的相反顺序;见12.6.2)。

     

7 delete-expression 将调用解除分配函数(3.7.3.2)。

在组合中,这些子句要求将调用析构函数(或数组的析构函数),并且将无条件地调用释放函数。如果抛出异常,这里没有规定不调用deallocation函数。

在1998年的标准中,语言律师和编译器开发人员可能会高兴地讨论与我上面所说的不同的解释。幸运的是,事情在后来的标准中更为明确......

Draft N4296 available from open-std.org中,相同的条款扩展如下:(从记忆中,官方标准中的措辞是相同的,但我当前的机器上没有副本)
(强调我的)

  

6如果 delete-expression 的操作数值不是空指针值, delete-expression 将调用析构函数(如果有)对象或要删除的数组的元素。在一个案例中   数组,元素将按地址递减的顺序销毁(即,与构造函数完成的顺序相反;见12.6.2)。

     

7如果 delete-expression 的操作数值不是空指针值,则:

     

(7.1) - 如果没有省略要删除的对象的 new-expression 的分配调用,并且没有扩展分配(5.3.4),    delete-expression 应调用解除分配函数(3.7.4.2)。从 new-expression 的分配调用返回的值应作为第一个参数传递给释放函数。

     

(7.2) - 否则,如果通过扩展另一个 new-expression 的分配来扩展或提供分配,并为每个其他指针扩展 delete-expression 已经评估了由扩展 new-expression 提供存储的 new-expression 生成的值,    delete-expression 应调用释放函数。从扩展 new-expression 的分配调用返回的值应作为第一个参数传递给释放函数。

     

(7.3) - 否则, delete-expression 将不会调用a   解除分配函数(3.7.4.2)。

     

否则,未指定是否将调用释放函数。 [注意: 无论对象的析构函数或数组的某个元素是否引发异常,都会调用释放函数。 - 结束注释 ]

最后的注释说明即使析构函数抛出异常,也必须调用释放函数。

我不确定该标准的哪个演变首先拼写出来,但基于上述内容,这些条款可能会保留在第5.3.5节(tag [expr.delete])。

答案 2 :(得分:-1)

在调用delete运算符之前调用析构函数。见cppreference - delete expression

  

如果expression不是空指针,则delete表达式为正在销毁的对象或被销毁的数组的每个元素调用析构函数(如果有)(从最后一个元素到数组的第一个元素) )。   之后,除非匹配的new-expression与另一个new-expression(自C ++ 14)组合,否则delete表达式将调用deallocation函数,运算符delete(对于表达式的第一个版本)或operator delete [](对于表达式的第二个版本)。

由于这种操作顺序,在调用重载版本的delete运算符之前,会调用析构函数并抛出异常。