关于c ++:当在多重继承中使用operator delete时,谁调用了类的析构函数

Who calls the Destructor of the class when operator delete is used in multiple inheritance

这个问题听起来可能太傻了,但是,我在别的地方找不到具体的答案。

对后期绑定的工作方式和继承中使用的虚拟关键字知之甚少。

在代码示例中,当继承的情况下使用指向在堆和删除运算符上创建的派生类对象的基类指针来释放内存时,只有在基析构函数声明为虚函数时,才会按顺序调用派生和基的析构函数。

现在我的问题是:

1)当base的析构函数不是虚的时候,为什么不调用派生dtor的问题只在使用"delete"操作符的情况下出现,为什么不在下面给出的情况下出现:

1
2
3
derived drvd;
base *bPtr;
bPtr = &drvd; //DTOR called in proper order when goes out of scope.

2) When"delete" operator is used, who is reponsible to call the destructor of the class? The operator delete will have an implementation to call the DTOR ? or complier writes some extra stuff ? If the operator has the implementation then how does it looks like , [I need sample code how this would have been implemented].

3) If virtual keyword is used in this example, how does operator delete now know which DTOR to call?

Fundamentaly i want to know who calls the dtor of the class when delete is used.

ZZU1〔1〕

I'm not sure if this is a duplicate, I couldn't find in search.

int main()
{
base *bPtr = new derived();

ZZU1〔2〕

}


+一个好问题。

看看虚拟机制是如何为非析构函数方法工作的,你会发现析构函数的行为没有什么不同。

有两种机制在起作用,可能会稍微混淆问题。.首先,一个非虚拟的机制发生在对象的构造和破坏上。对象是按照这个顺序从基类构造到派生类的,并且当销毁时,析构函数的顺序是相反的,因此派生到基类。这里没什么新鲜事。

考虑在派生类对象的基于类指针上调用非虚拟方法,会发生什么?调用基类实现。现在考虑从基类指针调用一个虚拟方法到一个派生类对象,会发生什么?将调用该方法的派生版本。什么都不知道。

现在让我们考虑一下析构函数场景。对具有非虚拟析构函数的派生类对象的基类指针调用Delete。调用了基类析构函数,如果该基类是从另一个类派生的,那么它的析构函数将在下一个类中被调用。因为虚拟机制没有发挥作用,所以不会调用派生的析构函数,因为销毁从您在层次结构中调用的析构函数开始,并一直工作到基类。

现在考虑虚拟析构函数的情况。基于指向派生类对象的类指针调用Delete。在基类指针上调用任何虚方法时会发生什么?将调用派生版本。所以我们的派生类析构函数被调用。在销毁过程中会发生什么,对象从派生析构函数销毁到基类,但这次我们在派生类级别开始销毁,因为是虚拟方法机制。

为什么具有非虚拟或虚拟析构函数的堆栈对象在超出范围时会从派生类析构函数销毁到基类?因为在这种情况下调用声明类的析构函数,而虚拟机制与此无关。


  • 这是因为在这种情况下,编译器知道要销毁的对象的所有信息,在这种情况下,该对象是drvd,属于derived类型。当drvd超出范围时,编译器插入代码以调用其析构函数

  • delete是编译器的关键字。当编译器看到delete时,它插入调用析构函数的代码和调用operator delete来释放内存的代码。请记住,keyword deleteoperater delete是不同的。

  • 当编译器看到keyword delete被用于指针时,它需要生成代码以进行适当的销毁。为此,它需要知道指针的类型信息。关于指针,编译器只知道指针类型,而不是指针指向的对象类型。指针指向的对象可以是基类或派生类。在某些情况下,对象的类型可能被非常清楚地定义,例如

  • 1
    2
    3
    4
    5
    void fun()
    {
        Base *base= new Derived();
        delete base;
    }

    但在大多数情况下,它不是,例如这个

    1
    2
    3
    4
    void deallocate(Base *base)
    {
        delete base;
    }

    因此编译器不知道要调用哪个析构函数的基或派生。这就是它的工作方式

  • 如果基类没有虚函数(成员函数或析构函数)。它直接插入THR代码来调用基类的析构函数
  • 如果基类具有虚函数,那么编译器将从vtable中获取析构函数的信息。
  • 如果析构函数不是虚拟的。vtable将具有基本析构函数的地址,这就是将要调用的地址。这是不对的,因为这里没有调用正确的析构函数。这就是为什么总是建议将基类的析构函数声明为虚拟的原因。
  • 如果析构函数是虚拟的,vtable将具有析构函数的正确地址,编译器将在那里插入正确的代码。

  • 您实例化派生类型,当它超出范围时,它调用析构函数,不管是虚拟的还是非虚拟的。
  • 编译器将生成调用析构函数的代码。并非所有这些都发生在编译时。代码生成可以,但可以查找在运行时发生的DTOR地址。考虑这样一种情况:派生类型多于1,并且使用基指针进行删除。
  • 基类析构函数需要是虚拟的才能使多态删除调用派生类型的dtor。
  • 如果您想了解更多信息,请尝试重载new和delete。


    编译器生成所有必要的代码,以正确的顺序调用析构函数,无论它是超出范围的堆栈对象或成员变量,还是正在删除的堆对象。