我有一个带有虚拟析构函数的动物
类和一个派生类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
的析构函数没有被调用,当我的基类析构函数是虚拟的?
请注意,虽然Cat
是动物
,但Cat
s数组不是动物
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资格:
据我所知,继承不是相似的…
在我的理解中,这是未定义的行为,因为(在7.6.2.9删除,p2,强调我的):
在单对象删除表达式中,删除的操作数的值可能是空指针值,也可能是以前的非数组new-表达式产生的指针值,或者是指向由这种new-表达式创建的对象的基类子对象的指针。如果不是,则行为未定义。在数组删除表达式中,删除的操作数的值可能是空指针值,也可能是以前的数组new-表达式产生的指针值…
这基本上意味着对于delete[]
,类型必须是new[]
中的确切类型(不允许像delete
这样的基类子对象)。
因此,出于这样的原因——在我看来,这一次是显而易见的——实现需要知道完整对象大小是多少,以便它可以迭代到下一个数组元素。
我写了反驳为什么这会有所不同的论点——但是经过一番思考(并阅读了评论)——我意识到这样的解决方案正在解决X-Y问题。