【问题标题】:Who calls the Destructor of the class when operator delete is used in multiple inheritance多重继承中使用delete时谁调用了类的析构函数
【发布时间】:2011-02-05 05:35:23
【问题描述】:

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

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

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

现在我的问题是:

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


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.

<h1> Sample Code </h1>

class base
{
    public:
    base(){
       cout<<"Base CTOR called"<<endl;
    }

    virtual ~base(){  
       cout<<"Base DTOR called"<<endl;
    }
};

class derived:public base
{
    public:
        derived(){
            cout<<"Derived CTOR called"<<endl;
        }

    ~derived(){
            cout<<"Derived DTOR called"<<endl;
     }
};

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

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

delete bPtr;// only when you explicitly try to delete an object
return 0;

}

【问题讨论】:

  • 小点,但你的问题说多重继承。这不是多重继承的例子,它只是普通的旧继承。

标签: c++ inheritance


【解决方案1】:
  1. 这是因为在这种情况下,编译器知道要销毁的对象的所有信息,在这种情况下是drvd,类型为derived。当drvd 超出范围时,编译器会插入代码来调用其析构函数

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

  3. 当编译器看到 keyword delete 被用作指针时,它需要生成代码以正确销毁它。为此,它需要知道指针的类型信息。编译器只知道指针的类型,而不是指针指向的对象的类型。指针指向的对象可能是基类或派生类。在某些情况下,对象的类型可能会非常明确地定义,例如

void fun()
{
    Base *base= new Derived(); 
    delete base;
}

但在大多数情况下并非如此,例如这个

void deallocate(Base *base)
{
    delete base;
}

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

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

【讨论】:

    【解决方案2】:

    +1 好问题顺便说一句。

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

    有两种机制在起作用,这可能会使问题有点混乱 . 首先,在对象的构造和销毁中发生了一种非虚拟的机制。对象是从基类构造到派生类的,按照这个顺序,当被破坏时,析构函数的顺序是相反的,所以派生到基类。这里没有什么新鲜事。

    考虑在指向派生类对象的基类指针上调用非虚方法,会发生什么?基类实现被调用。现在考虑从基类指针到派生类对象调用虚方法,会发生什么?调用该方法的派生版本。没有什么是你不知道的。

    现在让我们考虑析构函数场景。在基类指针上调用 delete,该指针指向具有非虚拟析构函数的派生类对象。基类析构函数被调用,如果基类是从另一个类派生的,那么接下来会调用它的析构函数。因为虚拟机制没有发挥作用,派生的析构函数不会被调用,因为析构函数从你在层次结构中调用的析构函数开始,一直到基类。

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

      • @Marcelo :如果一切都发生在编译时,为什么这个实现叫做后期绑定?以及编译器在编译时如何知道要调用哪个 DTOR?为我的无知道歉,感谢您的努力。
      • @dicaprio 编译器在编译时发出代码来处理销毁,使用虚函数机制。代码在运行时执行。
      • @Neil :非常感谢您的回复,这是否意味着编译器会编写额外的东西来使用虚拟机制,而操作符 delete 只是使用运行时的地址来调用正确的 DTOR 吗?
      • @dicaprio:当delete被调用时,编译器调用析构函数成员函数。如果该析构函数是虚拟的,则将调用派生最多的析构函数。此时,行为与相同类型的堆栈对象超出范围时发生的行为相同。
      • @dicaprio 它会发出额外的东西。
      【解决方案4】:
      1. 您实例化派生类型,当它超出范围时,它会调用析构函数,无论是否虚拟。
      2. 编译器将生成调用析构函数的代码。它并非全部发生在编译时。代码生成确实如此,但在运行时查找 dtor 的地址是什么。考虑一下您有超过 1 个派生类型并使用基指针执行删除操作的情况。
      3. 基类析构函数必须是虚拟的,才能对派生类型的 dtor 进行多态删除调用。

      如果您想了解更多信息,请尝试重载 new 和 delete。

      【讨论】:

      • @epronk :谢谢,我同意你的观点 1,但 2 和 3 仍然不清楚。这再次提出了许多问题,我在下面回答 Marcelo 时添加了这些问题。
      • base* bPtr 不会以任何方式延长对象的生命周期,并且会在 drvd 之前超出范围。 (星星在左边)
      • 好点!!您建议重载 new 或 delete 运算符,但是,我的理解是我们没有实现任何调用类的 dtor 的东西,对吗?
      猜你喜欢
      • 2015-06-29
      • 2016-10-09
      • 1970-01-01
      • 2016-03-23
      • 2017-07-09
      • 2012-12-09
      • 1970-01-01
      • 2014-10-11
      相关资源
      最近更新 更多