首页 文章

为什么在删除派生类对象时调用基类析构函数(虚拟)?

提问于
浏览
29

析构函数(当然还有构造函数)与其他成员函数之间的区别在于,如果常规成员函数在派生类中具有主体,则只会执行Derived类中的版本 . 在析构函数的情况下,派生以及基类版本都会被执行吗?

很容易知道在析构函数(可能是虚拟的)和构造函数的情况下究竟发生了什么,即使删除了最多派生的类对象,也会为它们的所有基类调用它们 .

提前致谢!

7 回答

  • 0

    标准说

    在执行析构函数的主体并销毁在主体内分配的任何自动对象之后,类X的析构函数调用X的直接非变体成员的析构函数,X的直接基类的析构函数,如果X是X的类型,则大多数派生类(12.6.2),它的析构函数调用X的虚拟基类的析构函数 . 所有析构函数都被调用,就像它们被引用了一个限定名称一样,即忽略了更多派生类中任何可能的虚拟覆盖析构函数 . 基础和成员按照构造函数完成的相反顺序销毁(见12.6.2) . 析构函数中的return语句(6.6.3)可能不会直接返回给调用者;在将控制转移给调用者之前,调用成员和基础的析构函数 . 数组元素的析构函数按其构造的相反顺序调用(见12.6) .

    此外,根据RAII资源需要与适当对象的生命周期相关联,并且必须调用各个类的析构函数来释放资源 .

    例如,以下代码泄漏内存 .

    struct Base
     {
           int *p;
            Base():p(new int){}
           ~Base(){ delete p; } //has to be virtual
     };
    
     struct Derived :Base
     {
           int *d;
           Derived():Base(),d(new int){}
           ~Derived(){delete d;}
     };
    
     int main()
     {
         Base *base=new Derived();
         //do something
    
         delete base;   //Oops!! ~Base() gets called(=>Memory Leak).
     }
    
  • 11

    Constructor and destructor are different from the rest of regular methods.

    Constructor

    • 不能是虚拟的

    • 在派生类中,您要么显式调用基类的构造函数

    • 或者,如果你没有调用基类构造函数编译器将插入调用 . 它将调用不带参数的基础构造函数 . 如果不存在这样的构造函数,则会出现编译器错误 .


    struct A {};
    struct B : A { B() : A() {} };
    
    // but this works as well because compiler inserts call to A():
    struct B : A { B() {} };
    
    // however this does not compile:
    struct A { A(int x) {} };
    struct B : A { B() {} };
    
    // you need:
    struct B : A { B() : A(4) {} };
    

    Destructor

    • 当您通过指针或引用在派生类上调用析构函数时,其中基类具有虚析构函数,将首先调用派生最多的析构函数,然后以相反的构造顺序调用其余派生类 . 这是为了确保所有内存都已正确清理 . 如果最后派生的类被调用,那么它将不起作用,因为到那时基类不会存在于内存中而你会得到段错误 .

    struct C
    {
        virtual ~C() { cout << __FUNCTION__ << endl; }
    };
    
    struct D : C
    {
        virtual ~D() { cout << __FUNCTION__ << endl; }
    };
    
    struct E : D
    {
        virtual ~E() { cout << __FUNCTION__ << endl; }
    };
    
    int main()
    {
        C * o = new E();
        delete o;
    }
    

    输出:

    ~E
    ~D
    ~C
    

    如果基类中的方法被标记为 virtual ,则所有继承的方法都是虚拟的,所以即使你没有将 DE 中的析构函数标记为 virtual ,它们仍然是 virtual ,它们仍然以相同的顺序被调用 .

  • 15

    这是设计的 . 必须调用基类上的析构函数才能释放其资源 . 经验法则是派生类应该只清理自己的资源并让基类自行清理 .

    C++ spec

    在执行析构函数的主体并销毁在主体内分配的任何自动对象之后,类X的析构函数调用X的直接成员的析构函数,X的直接基类的析构函数,如果X是派生类最多的类型(12.6.2),它的析构函数调用X的虚拟基类的析构函数 . 调用所有析构函数,就好像它们是使用限定名称引用一样,即忽略更多派生类中的任何可能的虚拟覆盖析构函数 . 基础和成员按照构造函数完成的相反顺序销毁(见12.6.2) .

    此外,因为只有一个析构函数,所以类必须调用哪个析构函数没有歧义 . 构造函数不是这种情况,如果没有可访问的默认构造函数,程序员必须选择应该调用哪个基类构造函数 .

  • 2

    因为这就是dtor的工作方式 . 当你创建一个对象时,从基数开始调用ctors,并一直到最大的派生 . 当您(正确地)销毁对象时,会发生相反的情况 . 使dtor虚拟产生差异的时间是,是否/当你通过指针(或引用,虽然这是相当不寻常的)对基类型销毁对象时 . 在那种情况下,替代方案并不是真的只有派生的dtor被调用 - 相反,替代方案只是未定义的行为 . 这种情况恰好采用仅调用派生的dtor的形式,但它也可能采用完全不同的形式 .

  • 2

    正如Igor所说,必须为基类调用构造函数 . 考虑如果不调用会发生什么:

    struct A {
        std::string s;
        virtual ~A() {}
    };
    
    struct B : A {};
    

    如果在删除 B 实例时不会调用 A 的析构函数,则永远不会清除 A .

  • 2

    基类析构函数可能负责清理由基类构造函数分配的资源 .

    如果您的基类有一个默认构造函数(一个不带参数或者它的所有参数都有默认值),那么在构造派生实例时会自动调用构造函数 .

    如果您的基类具有需要参数的构造函数,则必须在派生类构造函数的初始化列表中手动调用它 .

    由于析构函数不接受参数,因此在删除派生实例时将始终自动调用基类析构函数 .

    如果您正在使用多态并且您的派生实例由基类指针指向,那么只有在基本析构函数为虚拟时才会调用 derived 类析构函数 .

  • 11

    当任何对象被销毁时,析构函数将针对所有子对象运行 . 这包括通过包含重用和通过继承重用 .

相关问题