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

Why is an overloaded delete not called when an exception is thrown in a destructor?

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

异常抛出时,为什么delete运算符中的代码没有执行(并打印"bye")?

如果不应该执行,(如何)释放内存? 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) 重载删除。

我不需要关于在析构函数中抛出异常的糟糕做法的讲座 - 我只是 运行 进入类似的代码并且我对这种行为感到好奇。


我希望得到标准或类似参考文献支持的答案(如果存在此类参考文献)。

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

If expression is not a null pointer, the delete expression invokes the destructor (if any) for the object that's being destroyed, or for every element of the array being destroyed (proceeding from the last element to the first element of the array). After that, unless the matching new-expression was combined with another new-expression (since C++14) the delete expression invokes the deallocation function, either operator delete (for the first version of the expression) or operator delete[] (for the second version of the expression).

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

standard Draft N4296 5.3.5,第 121 页说:

[expr.delete] [ Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception. — end note ]

因此无论析构函数抛出什么,都必须调用 operator delete

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

测试的错误:

在 1998 年的 C++ 标准(ISO/IEC 14882 第一版,1998-09-01)中,删除表达式的工作原理在第 6 和第 7 段的 "Section 5.3.5 Delete [expr.delete]" 中非常简单地说明。

6 The delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2).

7 The delete-expression will call a deallocation function (3.7.3.2).

结合起来,这些子句要求调用析构函数(或数组的析构函数)并且无条件调用释放函数。这里没有规定抛出异常时不调用deallocation函数

在 1998 年的标准中,语言律师和编译器开发人员可能会喜欢争论与我上面所述不同的解释的诡辩。幸运的是,在后来的标准中,事情更加明确...

Draft N4296 available from open-std.org中将相同的条款展开如下:(凭记忆官方标准中的写法是一样的,但我现在的机器上没有)
(强调我的)

6 If the value of the operand of the delete-expression is not a null pointer value, the delete-expression will invoke the destructor (if any) for the object or the elements of the array being deleted. In the case of an array, the elements will be destroyed in order of decreasing address (that is, in reverse order of the completion of their constructor; see 12.6.2).

7 If the value of the operand of the delete-expression is not a null pointer value, then:

(7.1) - If the allocation call for the new-expression for the object to be deleted was not omitted and the allocation was not extended (5.3.4), the delete-expression shall call a deallocation function (3.7.4.2). The value returned from the allocation call of the new-expression shall be passed as the first argument to the deallocation function.

(7.2) - Otherwise, if the allocation was extended or was provided by extending the allocation of another new-expression, and the delete-expression for every other pointer value produced by a new-expression that had storage provided by the extended new-expression has been evaluated, the delete-expression shall call a deallocation function. The value returned from the allocation call of the extended new-expression shall be passed as the first argument to the deallocation function.

(7.3) - Otherwise, the delete-expression will not call a deallocation function (3.7.4.2).

Otherwise, it is unspecified whether the deallocation function will be called. [Note: The deallocation function is called regardless of whether the destructor for the object or some element of the array throws an exception.end note]

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

我不确定是哪个标准的演变首先阐明了内容,但根据上述情况,这些条款可能会保留在第 5.3.5 节(标签 [expr.delete])。