C++ 20中的“销毁操作符delete”是什么?

60

相关 https://dev59.com/Xb_qa4cB1Zd3GeqPO8b6 - Trilarion
1个回答

69
在 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必须是一个类成员函数。
  • 如果有多个可用的operator delete,一个销毁函数将始终优先于非销毁函数。
  • 非销毁和销毁operator delete签名之间的区别在于前者接收一个void *,而后者接收一个指向要删除对象类型的指针和一个虚拟参数std::destroying_delete_t
  • 与非销毁operator delete一样,销毁operator delete也可以采用可选的std::size_t和/或std::align_val_t参数,以相同的方式。它们的含义与以前相同,并且在虚拟参数std::destroying_delete_t之后。
  • 在销毁operator delete运行之前不会调用析构函数,因此预计需要自己调用析构函数。这也意味着对象仍然有效,在进行操作之前可以检查对象。
  • 使用非销毁operator delete时,通过基类指针调用派生对象的delete而没有虚拟析构函数是未定义行为。通过给基类提供销毁operator delete可以使其实现使用其他方式确定要调用的正确析构函数,从而使其安全和定义良好。

销毁operator delete的使用场景在P0722R1中详细说明。这里是一个简短的摘要:

  • 销毁operator delete允许具有变量大小数据的类保留大小为delete的性能优势。这通过在对象内部存储大小,并在调用析构函数之前在operator delete中检索它来实现。
  • 如果一个类将拥有子类,则同时分配的任何变量大小的数据必须放在对象的开始而不是结束。在这种情况下,唯一安全的方法是使用销毁operator delete来删除此类对象,以便可以确定分配的正确起始地址。
  • 如果一个类只有几个子类,它可以通过这种方式实现自己的动态分派析构函数,而无需使用虚函数表。这样稍微快一些,并且会导致较小的类大小。

以下是第三个用例的示例:

#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()。请参见错误#91859的评论2。)

8
通过销毁删除,即使派生类没有虚析构函数,使用一个指向基类的指针安全地删除派生类。但这不是让实现者承担责任来确保安全吗?该函数现在必须以某种方式调用正确的析构函数。 - Sebastian Redl
1
这个能用于实现侵入式指针吗,也就是说只有在没有所有者的情况下才会进行实际删除? - Deduplicator
1
@Deduplicator:实际上可能是,但在形式上不行,除非对围绕对象生命周期和delete运算符的有效操作数的措辞进行进一步更改。 - Ben Voigt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接