2010-07-16 110 views
25

析构函数(当然也是构造函数)和其他成员函数之间的区别在于,如果常规成员函数在派生类中具有正文,则仅执行Derived类中的版本。而在析构函数的情况下,派生以及基类版本都会被执行?为什么在删除派生类对象时调用基类析构函数(虚拟)?

在析构函数(可能是虚拟的)的情况下,知道究竟发生了什么真是太好了,即使删除了大多数派生类对象,也要调用它们的所有基类。

在此先感谢!

+0

不完全是你想要的,但它显示了编译器生成的析构函数会做什么。 http://stackoverflow.com/questions/1810163/c-copy-constructor-a-class-that-c​​ontains-other-objects/1810320#1810320 – 2010-07-16 05:09:46

回答

13

标准说

执行析构函数的身体和破坏人体内部分配的任何自动对象后, 一类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). 
} 
+0

@dreamlax:呵呵,谢谢,编辑:) – 2010-07-16 03:58:54

+0

*形成不良*表示它没有编译(编辑,意思是说没有编译)。上面的代码会编译,只是漏洞。 – 2010-07-16 04:02:19

+0

@Igor:我从来没有说过代码不会编译。我明确提到代码有内存泄漏。 – 2010-07-16 04:03:28

11

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

C++ spec

执行 析构函数的身体和破坏 体内分配的任何 自动对象之后,对于X类析构函数调用 的析构函数X的直接 成员,析构函数对于X的 直接基类,如果X是 类型的最派生类 (12.6.2),则其析构函数调用 析构函数为X的虚拟基地 c lasses。如果所有析构函数都以 限定名称引用,即忽略更多派生类中的任何 可能的虚拟重写 析构函数,则称它们为 。 基地和会员在 相反顺序销毁 其构造函数(见 12.6.2)。

此外,因为只有一个析构函数,所以类不得不调用哪个析构函数。构造函数并非如此,如果没有可访问的默认构造函数,程序员必须选择应调用哪个基类构造函数。

+0

*“程序员必须选择哪个基类构造函数”* - 这是只有在没有可访问的默认构造函数的情况下。 – 2010-07-16 04:45:23

+0

正确,让我澄清一点。 – 2010-07-16 04:50:37

2

因为这就是dtor的工作方式。当你创建一个对象时,ctors从基础开始被调用,并且一直到最大派生。当你正确地销毁对象时,会发生相反的情况。虚拟制作虚拟机的时间有所不同,如果/当你通过指针(或引用,尽管这很不寻常)摧毁一个对象到基本类型时。在这种情况下,替代方案并不是真的只有派生的dtor被调用 - 相反,替代方案只是未定义的行为。这种做法恰好采取只调用派生的dtor的形式,但它也可能采用完全不同的形式。

2

正如伊戈尔所说的构造函数必须被调用基类。考虑会发生什么,如果它就不叫:

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

struct B : A {}; 

如果删除B实例时A析构函数不会被调用,A永远不会被清理。

2

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

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

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

由于析构函数不带参数,所以在删除派生实例时,总会自动调用基类析构函数。

如果您在使用多态性与派生实例由基类指针指向,那么衍生如果基析构函数是虚拟的类析构函数只调用。

0

当任何对象被销毁时,析构函数会针对所有子对象运行。这包括通过遏制来重用和通过继承来重用。

7

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

构造

  • 不能在派生类中的虚拟
  • 你要么显式调用基类
  • 的构造函数,或在情况下你不调用基类的构造函数编译器将插入呼叫。它将调用没有参数的基础构造函数。如果不存在这样的构造函数,则会出现编译器错误。

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) {} }; 

析构

    当调用超过一个指针或参考,其中,所述基类有虚拟析构派生类的析构函数
  • ,最衍生析构函数将是先调用,然后按照构造的相反顺序调用其余的派生类。这是为了确保所有内存都已经被正确清理。如果派生类的最后一个被调用,那么它将不起作用,因为那时基类不会在内存中存在,并且会出现段错误。

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,他们仍然被调用以相同的顺序。