我有一个带有构造函数和析构函数的 Animal 类。 Cat 有一个私有的 Brain* 属性。 构建后,Cat 使用 new Brain() 创建他的 Brain; 销毁后,Cat 会删除他的 Brain。 我不明白为什么当我的基类析构函数是虚拟的时,猫和大脑的析构函数没有被调用?
#include <iostream>
using std::cout ;
using std::endl ;
class Brain {
public:
Brain (void){cout << "Brain constructor" << endl ;}
~Brain (void){cout << "Brain destructor" << endl ;}
} ;
class Animal
{
public:
Animal (void){cout << "Animal constructor" << endl ;}
virtual ~Animal (void){cout << "Animal destructor" << endl ;}
} ;
class Cat : public Animal
{
public:
Cat (void){
cout << "Cat constructor" << endl ;
myPointer = new Brain() ;
}
~Cat (void){
cout << "Cat destructor" << endl ;
delete myPointer ;
}
private:
Brain* myPointer ;
} ;
int main()
{
const Animal* j = new Cat[1] ;
delete [] j ;
}
给出输出
Animal constructor
Cat constructor
Brain constructor
Animal destructor
请注意,虽然
Cat
是一个 Animal
,但 Cat
s 的数组 not Animal
s 的数组。换句话说,数组在 C++ 中是 invariant,而不是像在其他语言中那样是 covariant。
所以你正在向上转换这个数组,这稍后会混淆编译器。在这种情况下,您必须在正确的原始类型上执行数组
delete[]
-Cat*
.
请注意,如果您分配了一个包含 2 个或更多
Cat
的数组,将其转换为一个 Animal*
,然后尝试使用第二个或后续的 Animal,您也会出于同样的原因遇到类似的问题。
我回答我自己的评论: https://en.cppreference.com/w/cpp/language/delete
The pointed-to type of expression must be similar to the element type of the array object
https://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing
通俗地说,如果忽略顶层,两种类型是相似的 简历资格:
- 它们是同一类型;或
- 都是指针,指向的类型相似;或者
- 它们都是指向同一个成员的指针 类,指向成员的类型相似;或
- 它们都是相同大小的数组或者都是未知边界的数组,并且 数组元素类型相似。 (直到 C++20)
- 它们都是相同大小的数组,或者至少其中一个是未知数组 bound,数组元素类型类似。
据我了解,继承不是相似性...
在我的理解中这是未定义的行为,因为(在 7.6.2.9 删除,p2,强调我的):
在单对象删除表达式中,操作数的值 delete 可能是一个空指针值,一个由 先前的非数组新表达式,或指向基类的指针 由这样的新表达式创建的对象的子对象。如果不是,则 行为未定义。在数组删除表达式中,值 delete 的操作数 可能是一个空指针值 或者一个指针 由先前数组新表达式...
产生的值
这基本上意味着对于
delete[]
,类型必须是来自new[]
的确切类型(不允许像delete
这样的基类子对象)。
所以出于这样的原因——在我看来这次很明显——实现需要知道完整对象的大小是多少,以便它可以迭代到下一个数组元素。
Counter 参数是因为目前实现无论如何都需要存储数组元素的数量(所以它知道要解构多少)——它也可能存储完整的类型/大小。
对于在引擎盖下执行多态捕获匹配(并且也是标准强制要求)的例外情况也是如此。