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++就不会调用析构函数链。