suh*_*han 43 c++ arrays polymorphism inheritance dynamic-memory-allocation
我有一个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;
}
Run Code Online (Sandbox Code Playgroud)
这给出了输出:
动物构造函数
猫构造函数
动物析构函数
我不明白Cat当我的基类析构函数是虚拟的时,为什么不调用 的析构函数?
Mik*_*ine 42
请注意,虽然 aCat是 an ,但sAnimal的数组不是s的数组。换句话说,数组在 C++ 中是不变的,而不是像其他语言中那样是协变的。CatAnimal
所以你要向上转换这个数组,这会让编译器感到困惑。delete[]在这种情况下,您必须在正确的原始类型 - 上执行数组Cat*。
Cat请注意,如果您分配了 2 个或更多s 的数组,将其转换为 anAnimal*然后尝试使用第二个或后续的 Animal,您会出于同样的原因遇到类似的问题。
AnA*_*ons 10
根据我的理解,这是未定义的行为,因为(在 7.6.2.9 删除,p2 中,强调我的):
在单对象删除表达式中,delete 操作数的值可以是空指针值、由先前的非数组 new 表达式产生的指针值,或者指向由以下方法创建的对象的基类子对象的指针:如此新的表达方式。如果不是,则行为未定义。在数组删除表达式中,删除操作数的值可以是空指针值 或由先前数组新表达式产生的指针值...
这基本上意味着该delete[]类型必须与来自的类型完全相同new[](不允许有基类子对象,例如delete)。
因此,出于这样的原因 - 在我看来,这次是显而易见的 - 实现需要知道完整对象大小是多少,以便它可以迭代到下一个数组元素。
我写了反驳论据来解释为什么这可能会有所不同 - 但经过一番思考(并阅读评论) - 我意识到这样的解决方案正在解决 XY 问题。
我回答我自己的评论: https://en.cppreference.com/w/cpp/language/delete(强调我的)
对于第二种(数组)形式,表达式必须是空指针值或先前由 new-expression 的数组形式获得的指针值,其分配函数不是非分配形式(即重载(10))。表达式指向的类型必须与数组对象的元素类型相似。如果表达式是其他任何内容,包括如果它是通过 new-expression 的非数组形式获得的指针,则行为未定义。
https://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing
非正式地说,如果忽略顶级简历资格,则两种类型是相似的:
- 它们是同一类型;或者
- 它们都是指针,并且指向的类型相似;或者
- 它们都是指向同一个类的成员的指针,并且所指向的成员的类型相似;或者
- 它们都是相同大小的数组或都是未知边界的数组,并且数组元素类型相似。(直到 C++20)
- 它们都是大小相同的数组或者至少其中一个是未知边界数组,并且数组元素类型相似。
据我了解,继承不是相似性......
我对其他答案的唯一批评是它们太温和了。数组之所以有效,是因为所有条目都具有相同的大小,因此可以轻松计算出索引为 5 的条目的起始地址。将 Derived 数组转换为 Base 数组(这就是您在这里所做的)可能会产生比您所显示的更糟糕的后果。(如果数组恰好只有一个条目或者 Derived 没有新的数据成员,那么您可能没问题。) A[1].method() 可能会做一些奇怪的事情,因为 (Base*)(A)+1 甚至不是任何对象的起始地址。该方法将读取错误的数据,并且任何更改的影响都将是高度不可预测的。对虚函数的任何调用都将使用完全无意义的 vtable 指针,因此谁知道将执行什么“代码”。即使该调用不会立即造成灾难,您也可能会覆盖 vtable 指针。销毁数组几乎肯定会删除一些无意义的“指针”。
指向单个对象的指针(在没有“[]”的情况下分配和删除)是可以的。您可以转换为 Base*、dynamic_cast 返回,并调用虚函数。这就是 Ayxan Haqverdili 的建议有效的原因。X 的数组不应该包含除运行时类型 X 的对象之外的任何内容,并且 new[] (甚至 new[1])创建数组。