提问者:小点点

为什么在通过指向基类的指针删除数组时没有调用派生类的(虚拟)析构函数?


我有一个带有虚拟析构函数的动物类和一个派生类Cat

#include <iostream>

struct Animal
{
    Animal() { std::cout << "Animal constructor" << std::endl; }
    virtual ~Animal() { std::cout << "Animal destructor" << std::endl; }
};

struct Cat : public Animal
{
    Cat() { std::cout << "Cat constructor" << std::endl; }
    ~Cat() override { std::cout << "Cat destructor" << std::endl; }
};

int main()
{
    const Animal *j = new Cat[1];
    delete[] j;
}

这给出了输出:

动物构造器
Cat构造器
动物构造器

我不明白为什么Cat的析构函数没有被调用,当我的基类析构函数是虚拟的?


共3个答案

匿名用户

请注意,虽然Cat动物,但Cats数组不是动物s数组。换句话说,数组在C中是不变的,而不是像在其他一些语言中那样是协变的。

所以你正在向上转换这个数组,这后来会混淆编译器。在这种情况下,您必须在正确的原始类型-Cat*上执行数组delete[]

请注意,如果您分配了一个包含2个或更多Cat的数组,将其强制转换为动物*,然后尝试使用第二个或后续的动物,您也会遇到类似的问题。

匿名用户

我回答我自己的评论:https://en.cppreference.com/w/cpp/language/delete(强调我的)

对于第二种(数组)形式,表达式必须是空指针值或先前由new-表达式的数组形式获得的指针值,其分配函数不是非分配形式(即重载(10))。表达式的指向类型必须类似于数组对象的元素类型。如果表达式是其他任何东西,包括如果它是new-表达式的非数组形式获得的指针,则行为未定义。

https://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing

非正式地,两种类型是相似的if,忽略顶级cv资格:

  • 它们是相同的类型;或
  • 它们都是指针,指向的类型相似;或
  • 它们都是指向同一类成员的指针,指向成员的类型相似;或
  • 它们都是大小相同的数组或都是未知界限的数组,数组元素类型相似。(直到C 20)
  • 它们都是大小相同的数组或者至少其中一个是未知界数组,数组元素类型相似。

据我所知,继承不是相似的…

匿名用户

在我的理解中,这是未定义的行为,因为(在7.6.2.9删除,p2,强调我的):

在单对象删除表达式中,删除的操作数的值可能是空指针值,也可能是以前的非数组new-表达式产生的指针值,或者是指向由这种new-表达式创建的对象的基类子对象的指针。如果不是,则行为未定义。在数组删除表达式中,删除的操作数的值可能是空指针值,也可能是以前的数组new-表达式产生的指针值…

这基本上意味着对于delete[],类型必须是new[]中的确切类型(不允许像delete这样的基类子对象)。

因此,出于这样的原因——在我看来,这一次是显而易见的——实现需要知道完整对象大小是多少,以便它可以迭代到下一个数组元素。

我写了反驳为什么这会有所不同的论点——但是经过一番思考(并阅读了评论)——我意识到这样的解决方案正在解决X-Y问题。