C++20 中的 "destroying operator delete" 是什么?

What is "destroying operator delete" in C++20?

C++20 引入了“销毁 operator delete": new overloads of operator delete that take a tag-type std::destroying_delete_t 参数。

这到底是什么,什么时候有用?

在 C++20 之前,对象的析构函数总是在调用它们的 operator delete 之前被调用。在 C++20 中销毁 operator deleteoperator delete 可以改为调用析构函数本身。这是非破坏与破坏的非常简单的玩具示例 operator delete:

#include <iostream>
#include <new>

struct Foo {
    ~Foo() {
        std::cout << "In Foo::~Foo()\n";
    }

    void operator delete(void *p) {
        std::cout << "In Foo::operator delete(void *)\n";
        ::operator delete(p);
    }
};

struct Bar {
    ~Bar() {
        std::cout << "In Bar::~Bar()\n";
    }

    void operator delete(Bar *p, std::destroying_delete_t) {
        std::cout << "In Bar::operator delete(Bar *, std::destroying_delete_t)\n";
        p->~Bar();
        ::operator delete(p);
    }
};

int main() {
    delete new Foo;
    delete new Bar;
}

并且输出:

In Foo::~Foo()
In Foo::operator delete(void *)
In Bar::operator delete(Bar *, std::destroying_delete_t)
In Bar::~Bar()

关于它的主要事实:

  • 销毁 operator delete 函数必须是 class 成员函数。
  • 如果有多个 operator delete 可用,破坏性的将始终优先于非破坏性的。
  • 非销毁和销毁签名的区别operator delete前者接收一个void *,后者接收一个指向被删除对象类型的指针和一个dummy std::destroying_delete_t参数。
  • 和非销毁operator delete一样,销毁operator delete也可以带一个可选的std::size_tand/orstd::align_val_t参数,方法相同。这些意思和他们一直做的一样,他们追求虚拟 std::destroying_delete_t 参数。
  • 在销毁 operator delete 运行 之前不会调用析构函数,因此它应该自己这样做。这也意味着该对象仍然有效,可以在这样做之前进行检查。
  • 对于非破坏性 operator delete,通过指向基 class 的指针在派生对象上调用 delete 而没有虚拟析构函数是未定义的行为。这可以通过给基 class 一个破坏性的 operator delete 来变得安全和明确定义,因为它的实现可以使用其他方法来确定要调用的正确析构函数。

销毁 operator delete 的用例在 P0722R1 中有详细说明。这是一个快速摘要:

  • 销毁 operator delete 允许 class 末尾具有可变大小数据的 es 保留大小 delete 的性能优势。这通过将大小存储在对象中,并在调用析构函数之前在 operator delete 中检索它来实现。
  • 如果 class 将有子classes,同时分配的任何可变大小数据必须在对象开始之前,而不是结束之后。在这种情况下,delete 此类对象的唯一安全方法是销毁 operator delete,以便可以确定正确的分配起始地址。
  • 如果 class 只有几个子class,它可以通过这种方式为析构函数实现自己的动态调度,而不需要使用 vtable。这样速度稍快,并且 class 尺寸更小。

这是第三个用例的示例:

#include <iostream>
#include <new>

struct Shape {
    const enum Kinds {
        TRIANGLE,
        SQUARE
    } kind;

    Shape(Kinds k) : kind(k) {}

    ~Shape() {
        std::cout << "In Shape::~Shape()\n";
    }

    void operator delete(Shape *, std::destroying_delete_t);
};

struct Triangle : Shape {
    Triangle() : Shape(TRIANGLE) {}

    ~Triangle() {
        std::cout << "In Triangle::~Triangle()\n";
    }
};

struct Square : Shape {
    Square() : Shape(SQUARE) {}

    ~Square() {
        std::cout << "In Square::~Square()\n";
    }
};

void Shape::operator delete(Shape *p, std::destroying_delete_t) {
    switch(p->kind) {
    case TRIANGLE:
        static_cast<Triangle *>(p)->~Triangle();
        break;
    case SQUARE:
        static_cast<Square *>(p)->~Square();
    }
    ::operator delete(p);
}

int main() {
    Shape *p = new Triangle;
    delete p;
    p = new Square;
    delete p;
}

它打印这个:

In Triangle::~Triangle()
In Shape::~Shape()
In Square::~Square()
In Shape::~Shape()

(注意:当启用优化时,GCC 11.1 及更早版本将错误地调用 Triangle::~Triangle() 而不是 Square::~Square()。请参阅 comment 2 of bug #91859。)