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

Ked*_*arX 32 c++ inheritance destructor

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

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

提前致谢!

Pra*_*rav 16

标准说

在执行析构函数的主体并销毁在主体内分配的任何自动对象之后,类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).
 }
Run Code Online (Sandbox Code Playgroud)

  • 你说*形成错误的*,这意味着没有*格式良好的*,从规范中,它意味着**根据语法规则构建的C++程序,可诊断的语义规则和一个定义规则** - 即不编译. (2认同)

ste*_*anB 13

构造函数和析构函数与常规方法的其他方法不同.

构造函数

  • 不可能是虚拟的
  • 在派生类中,您要么显式调用基类的构造函数
  • 或者,如果你没有调用基类构造函数编译器将插入调用.它将调用不带参数的基础构造函数.如果不存在这样的构造函数,则会出现编译器错误.

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) {} };
Run Code Online (Sandbox Code Playgroud)

析构函数:

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

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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

~E
~D
~C
Run Code Online (Sandbox Code Playgroud)

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


Igo*_*aka 11

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

来自C++规范:

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

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