关于c ++:多态类中的虚析构函数

virtual destructor in polymorphic classes

我理解,只要您有一个多态的基类,这个基类就应该定义一个虚拟析构函数。因此,当删除指向派生类对象的基类指针时,它将首先调用派生类的析构函数。如果我错了,请纠正我。

此外,如果基类析构函数是非虚拟的,则删除指向派生对象的基类指针是未定义的行为。如果我错了也纠正我。

所以我的问题是:为什么当基类析构函数是非虚拟的时,对象不会被正确地销毁?

我假设这是因为虚函数有某种表,每当调用虚函数时,都会记住并查询该表。编译器知道当一个对象应该被删除时,它应该首先调用派生的析构函数。

我的假设正确吗?


如果在删除对象时,变量的静态类型是BAS类型,那么将调用基类型的析构函数,但不会调用子类的析构函数(因为它不是虚拟的)。

因此,基类分配的资源将被释放,而子类分配的资源将不会被释放。

这样就不会正确地破坏对象。

关于该表,您是正确的:它被称为虚拟方法表或"vtable"。但是,析构函数非虚拟化的结果并不是析构函数没有按正确的顺序调用,而是根本没有调用子类的析构函数!


考虑

1
2
3
4
5
6
7
8
struct Base {
    void f() { printf("Base::f"); }
};
struct Derived : Base {
    void f() { printf("Derived::f"); }
};
Base* p = new Derived;
p->f();

这将打印Base::f,因为Base::f不是虚拟的。现在对析构函数也这样做:

1
2
3
4
5
6
7
8
struct Base {
    ~Base() { printf("Base::~Base"); }
};
struct Derived : Base {
    ~Derived() { printf("Derived::~Derived"); }
};
Base* p = new Derived;
p->~Base();

这打印了Base::~Base。现在,如果我们将析构函数设为虚拟的,那么,与任何其他虚拟函数一样,调用对象动态类型中的最后一个重写器。析构函数重写基类中的虚拟析构函数(即使其"名称"不同):

1
2
3
4
5
6
7
8
struct Base {
    virtual ~Base() { printf("Base::~Base"); }
};
struct Derived : Base {
    ~Derived() override { printf("Derived::~Derived"); }
};
Base* p = new Derived;
p->~Base();

调用p->~Base()实际上调用Derived::~Derived()。因为这是一个析构函数,所以在主体完成执行之后,它会自动调用基和成员的析构函数。所以输出是

1
2
Derived::~Derived
Base::~Base

现在,一个删除表达式通常等价于一个析构函数调用,然后调用一个内存释放函数。在这种特殊情况下,表达式

1
delete p;

等于

1
2
p->~Base();
::operator delete(p);

因此,如果析构函数是虚拟的,那么这是正确的:它首先调用Derived::~Derived,然后在完成后自动调用Base::~Base。如果析构函数不是虚拟的,可能的结果是只调用Base::~Base