首页 > 代码库 > C++学习之构造函数和拷贝控制--什么样的情况下才需要虚析构函数

C++学习之构造函数和拷贝控制--什么样的情况下才需要虚析构函数

什么样的情况下才需要虚析构函数?

类需要控制自己的对象执行一系列操作时发生什么样的行为,这些操作包括:创建(对象)、拷贝、移动、赋值和销毁。在继承体系中,如果一个类(基类或其派生的类)没有定义拷贝控制操作,则编译器将自动的为其合成一个。即为合成的拷贝控制

基类拷贝控制中,由于继承关系导致的最大影响就是:基类通常应该定义一个‘虚析构函数’。用以动态的分配继承体系中的对象。

如:类A,B,C,D有如下继承关系(代码1):

1
2
3
4
class A;
class B:public A;
class C:public B;
class D:public C;

其中:类A定义如下(代码2):

1
2
3
4
5
class A {
public:
    //其他函数
    virtual ~A()=default;//用于动态绑定的析构函数

}

当我们delete一个A* item 类型的指针时,该指针可能是指向A的,也可能指向的是B,C,D中的一个,编译器在delete时必须弄清楚到底应该执行A,B,C,D中哪一个类的析构函数。此时需要编译器进行动态绑定(即只有运行时才能知道到底item 指向的是那个类)。当在基类A中定义的析构函数为虚析构函数时,无论A的派生类(B,C,D)使用的是合成的析构函数还是自己定义的析构函数,它们都是虚析构函数。说人话就是:你老祖姓虚,传到你还是姓虚,你儿子孙子都得姓虚(千万别较真女生~~~),不管这儿孙是你血缘的还是你自己领养的,都得虚!

举个例子(代码3):

1
2
3
4
A *item = new A;  //此时item指向的就是A,静态类型于动态类型一致(这就是你本人)
delete item;  //调用A自己的析构函数(自杀了,杀的是你自己)
item = new B;  //静态类型为A,动态类型为B(此时你的血脉传到了你儿子身上,item是你儿子了!)
delete item;   //调用B自己的析构函数(你儿子要自杀,此时死的是你儿子,和你无关)

如果基类A的析构函数不是虚的(虚函数),则delete时,如果item指向的不是A,而是B或其他A的派生类,则会产生未定义的行为,未定义的行为通常会导致BUG。

那么问题来了:什么样的情况下才需要虚析构函数呢?是所有类都应该有吗?

通过基类的指针删除派生类的对象时,基类的析构函数应该是虚的。否则其删除效果将无法实现。

简单解释一下,派生类B中所有的属性以操作(Bp)不仅有B自己定义的属性、操作(Bself),还有继承自A的属性、操作(Aself),即Bp=Bself+Aself;

如代码3,当delete一个指向B的item时(其实item的类类型为A),如果A中的析构函数不是虚的,则只会删除Aself部分,因为item的类类型其实是A,只是指向了其派生类对象。但是在A的析构函数里其实并没有Bself部分,那这部分就删不掉了--这就是所谓的内存泄漏!只有A的析构函数是虚的,才能删除的不仅有Aself,还有Bself,即Bp全部被删除了。这才是正确的。

同时,并不是所有类都需要将析构函数定义成虚的。因为编译器在编译时会给类添加一个虚函数表,里面来存放虚函数指针,如果都定义成虚的,这样就会增加类的存储空间。浪费了!不用作基类,也不需要为虚的!不需要通过基类的指针操作派生类的对象时,基类的析构函数应该是虚的。

这里借用一下文章代码:什么时候要用虚析构函数? 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ClxBase
{
public:
     ClxBase() {};
     virtual ~ClxBase() {};
     virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase
{
public:
     ClxDerived() {};
     ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
     void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
     代码
ClxBase *pTest = new ClxDerived;
pTest->DoSomething();
delete pTest;

正常情况应该输出:

1
2
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!

如果将类ClxBase的析构函数定义为非虚(去掉前面的那个virtual),则输出为:

1
Do something in class ClxDerived!

根本没有调用ClxDerived的析构函数哦~~~

同样,在什么时候要用虚析构函数? 中,提出了一个这样的问题:

为什么继承一个没有虚析构函数的类是危险的?

这个问题吗其实上面已经解释过了,会导致删不完!内存泄漏问题。当你公有继承创建一个从基类继承的相关类时,指向新类对象中的指针和引用实际上都指向了起源的对象。因为析构函数不是虚函数,所以当你delete一个这样的类时,C++就不会调用析构函数链。

 

 

 

 

 



来自为知笔记(Wiz)



C++学习之构造函数和拷贝控制--什么样的情况下才需要虚析构函数