C++ “什么是”呢;删除运算符“删除”;在C++;20?

C++ “什么是”呢;删除运算符“删除”;在C++;20?,c++,destructor,c++20,delete-operator,destroy,C++,Destructor,C++20,Delete Operator,Destroy,C++20引入了“销毁”:使用标记类型参数的运算符delete的新重载 这到底是什么,什么时候有用?在C++20之前,对象的析构函数总是在调用其操作符delete之前被调用。使用C++20中的销毁运算符delete,运算符delete可以调用析构函数本身。下面是一个非常简单的玩具示例,介绍了非销毁与销毁运算符删除的比较: #include <iostream> #include <new> struct Foo { ~Foo() { std::c

C++20引入了“销毁”:使用标记类型参数的
运算符delete的新重载


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

在C++20之前,对象的析构函数总是在调用其
操作符delete
之前被调用。使用C++20中的销毁
运算符delete
运算符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;
}
关于它的关键事实:

  • 销毁
    运算符delete
    函数必须是类成员函数
  • 如果有多个
    运算符delete
    可用,则销毁运算符始终优先于非销毁运算符
  • 非销毁和销毁
    运算符delete
    的签名之间的区别在于前者接收一个
    void*
    ,后者接收一个指向被删除对象类型的指针和一个伪
    std::destroming_delete_t
    参数
  • 与非销毁
    运算符删除
    一样,销毁
    运算符删除
    也可以采用可选的
    std::size\t
    和/或
    std::align\u val\t
    参数,方法相同。这意味着它们总是做同样的事情,并且它们在伪
    std::destroming\u delete\u t
    参数后面
  • 在销毁
    操作符delete
    运行之前,不会调用析构函数,因此它应该自己调用。这也意味着对象仍然有效,并且可以在执行此操作之前进行检查
  • 使用非销毁的
    运算符delete
    ,通过指向基类的指针而不使用虚拟析构函数对派生对象调用
    delete
    ,是未定义的行为。这可以通过赋予基类a销毁
    操作符delete
    来实现,因为它的实现可以使用其他方法来确定要调用的正确析构函数,从而使其安全且定义良好
中详细介绍了销毁
操作员删除
的用例。下面是一个简短的总结:

  • 销毁
    operator delete
    允许在末尾具有可变大小数据的类保留大小
    delete
    的性能优势。其工作原理是将大小存储在对象中,并在调用析构函数之前在
    operator delete
    中检索它
  • 如果一个类将有子类,那么同时分配的任何可变大小的数据必须在对象开始之前,而不是在对象结束之后。在这种情况下,删除此类对象的唯一安全方法是销毁
    操作员删除
    ,以便确定正确的分配起始地址
  • 如果一个类只有几个子类,那么它可以通过这种方式为析构函数实现自己的动态分派,而不需要使用vtable。这稍微快一点,结果是班级规模更小
下面是第三个用例的示例:

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

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

“销毁delete可以通过指向基类的指针安全地删除派生类,即使它没有虚拟析构函数。”-它不是把确保安全的责任放在了销毁删除的实现者身上吗?该函数现在必须以某种方式调用正确的析构函数。这是否也可以用于实现入侵指针,这意味着只有在没有所有者离开的情况下才会执行实际删除?@Deduplicator:实际上可能是,但实际上不是,除非对对象生存期的措辞和
delete
运算符的有效操作数做了进一步的更改。这篇文章将在相关的meta中讨论