在 C++ 继承中,当指向基类的指针对象指向派生类的数组时,不调用派生类析构函数

问题描述 投票:0回答:3

我有一个带有构造函数和析构函数的 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
c++ c++11 inheritance polymorphism c++14
3个回答
14
投票

请注意,虽然

Cat
是一个
Animal
,但
Cat
s 的数组 not
Animal
s 的数组。换句话说,数组在 C++ 中是 invariant,而不是像在其他语言中那样是 covariant

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

delete[]
-
Cat*
.

请注意,如果您分配了一个包含 2 个或更多

Cat
的数组,将其转换为一个
Animal*
,然后尝试使用第二个或后续的 Animal,您也会出于同样的原因遇到类似的问题。


4
投票

我回答我自己的评论: 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,数组元素类型类似。

据我了解,继承不是相似性...


0
投票

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

单对象删除表达式中,操作数的值 delete 可能是一个空指针值,一个由 先前的非数组新表达式,或指向基类的指针 由这样的新表达式创建的对象的子对象。如果不是,则 行为未定义。在数组删除表达式中,值 delete 的操作数 可能是一个空指针值 或者一个指针 由先前数组新表达式...

产生的值

这基本上意味着对于

delete[]
,类型必须是来自
new[]
的确切类型(不允许像
delete
这样的基类子对象)。

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

Counter 参数是因为目前实现无论如何都需要存储数组元素的数量(所以它知道要解构多少)——它也可能存储完整的类型/大小。

对于在引擎盖下执行多态捕获匹配(并且也是标准强制要求)的例外情况也是如此。

© www.soinside.com 2019 - 2024. All rights reserved.