我有一个带有虚析构函数的Animal
类和一个派生类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
的析构函数,当我的基类析构函数是虚的时?
6条答案
按热度按时间ekqde3dh1#
注意,虽然
Cat
是Animal
,但Cat
s的数组 * 不是 *Animal
s的数组。换句话说,数组在C++中是 * 不变的 *,而不是像在其他语言中那样的 * 协变的 *。所以你向上转换了这个数组,这以后会混淆编译器。在这种情况下,必须在正确的原始类型-
Cat*
上执行数组delete[]
。请注意,如果您分配了一个包含2个或更多
Cat
的数组,将其转换为Animal*
,然后尝试使用第二个或后续Animal,则会出现类似的问题。oxf4rvwz2#
我回答我自己的评论:https://en.cppreference.com/w/cpp/language/delete(强调我的)
对于第二种(数组)形式,expression必须是空指针值或先前由分配函数不是非分配形式的new-expression的数组形式(即,过载(10))。表达式的指向类型必须与数组对象的元素类型相似。如果expression是其他任何东西,包括如果它是通过new-expression的非数组形式获得的指针,则行为未定义。
https://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing
非正式地说,如果忽略顶级cv限定,两种类型是相似的:
据我所知,遗传不是相似性…
but5z9lq3#
在我的理解中,这是未定义的行为,因为(在7.6.2.9中删除,p2,强调我的):
在单对象delete表达式中,delete的操作数的值可以是空指针值,也可以是前一个非数组new表达式的指针值,或者是指向由这样的new表达式创建的对象的基类子对象的指针。如果不是,则行为未定义。在数组delete表达式中,delete的操作数的值可以是空指针值****,也可以是前一个数组new-expression...的指针值。
这基本上意味着,对于
delete[]
,类型必须与new[]
完全相同(不允许像delete
这样的基类子对象)。因此,原因是这样的-在我看来,这一次是显而易见的-实现需要知道完整的对象大小是多少,以便它可以迭代到下一个数组元素。
rkkpypqq4#
我对其他答案唯一的批评是它们是两个驯服的。数组之所以有效,是因为所有条目都具有相同的大小,所以索引为5的条目的起始地址可以很容易地计算出来。将Derived数组转换为Base数组(这就是您在这里所做的)可能会产生比您所展示的更糟糕的结果。(如果数组碰巧只有一个条目或Derived没有新的数据成员,你 * 可能 * 没问题。)A[1].method()可能会做一些奇怪的事情,因为(Base*)(A)+1甚至不是任何对象的起始地址。该方法将读取错误的数据,并且任何更改的影响都将是高度不可预测的。任何对虚函数的调用都将使用一个完全没有意义的vtable指针,所以谁知道会执行什么“代码”。即使该调用不是立即的灾难,您也可以覆盖vtable指针。销毁数组几乎肯定会删除一些无意义的“指针”。
指向单个对象的指针(分配和删除时不带“[]”)正常。可以强制转换为Base*、dynamic_cast back和调用虚函数。这就是为什么Ayxan Haqverdili的建议有效。X的数组除了运行时类型X的对象之外不应该有任何东西,并且new[](甚至new[1])创建数组。
wswtfjt75#
其他人已经解释了这个问题,但是如果你想要一个多态对象数组,你需要一个指针数组。这里有一个方法:
lo8azlld6#
使Cat析构函数也是虚拟的,代码就可以按预期工作了。