首页 文章

在多重继承中使用operator delete时,谁调用类的析构函数

提问于
浏览
5

这个问题可能听起来太傻了,但是,我没有找到具体的答案 .

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

与代码示例一样,在继承的情况下,指向在heap和delete运算符上创建的派生类对象的基类指针用于释放内存,派生和基类的析构函数将仅按顺序调用当基础析构函数声明为虚函数时 .

现在我的问题是:

1)当base的析构函数不是虚拟的时,为什么只有在使用“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;

}

4 回答

  • 2

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

  • 1
    • 这是因为在这种情况下,编译器知道要被破坏的对象的所有内容,在这种情况下是 drvd ,类型为 derived . 当 drvd 超出范围时,编译器会插入代码来调用其析构函数

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

    • 当编译器将 keyword delete 用于指针时,需要生成适当销毁的代码 . 为此,它需要知道指针的类型信息 . 编译器对指针的唯一了解是指针类型,而不是指针指向的对象类型 . 指针指向的对象可以是基类或派生类 . 在某些情况下,例如可以非常清楚地定义对象的类型

    void fun()
    {
    Base * base = new Derived();
    删除基数;
    }

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

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

    所以编译器不知道调用base或派生的析构函数 . 这是它的工作方式

    • 如果Base类没有虚函数(成员函数或析构函数) . 它直接插入thr代码来调用基类的析构函数

    • 如果Base类具有虚函数,则编译器从vtable中获取destructer的信息 .

    • 如果destructer不是虚拟的 . vtable将具有base destructer的地址,这就是所谓的 . 这是不对的,因为这里没有调用正确的析构函数 . 这就是为什么始终建议将基类的析构函数声明为虚拟的原因

    • 如果析构函数是虚拟的,则vtable将具有destructer的更正地址,编译器将在那里插入正确的代码

  • 2

    1个好问题BTW .

    看看虚拟机制如何为非析构函数方法工作,你会发现析构函数的行为没有区别 .

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

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

    现在让我们考虑析构函数场景 . 在对具有非虚析构函数的派生类对象的基类指针上调用delete . 调用基类析构函数,如果基类是从另一个类派生的,那么它的析构函数将被调用 . 因为虚拟机制不起作用,所以不会调用派生的析构函数,因为破坏从您在层次结构中调用的析构函数开始,然后一直向下运行到基类 .

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

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

  • 1
    • 实例化派生类型,当它超出范围时,它会调用析构函数,无论是否为虚函数 .

    • 编译器将生成调用析构函数的代码 . 它并非都发生在编译时 . 代码生成确实如此,但查看dtor的地址是在运行时发生的 . 考虑一下你有多于1个派生类型并使用基指针进行删除的情况 .

    • 基类析构函数需要是虚拟的,才能对派生类型的dtor进行多态删除调用 .

    如果你想了解更多,请尝试重载new和delete .

相关问题