大家好
欢迎回来继续学习
C++语言程序设计
除了类的一般成员函数
可以是虚函数以外
析构函数也可以是虚函数
那么什么情况下
我们需要将析构函数
写成虚函数呢
如果你打算允许
其他的人
其他的程序代码通过基类的指针
去调用
派生类对象的析构函数
那就需要让基类的虚构函数
成为虚函数
比如说
我们用delete去释放空间的时候
就有可能出现这样的问题
那么在这种情况下
你不用虚函数的话
那么这个析构的过程呢
结果可能就是不确定的
大家可能还不太理解这句话
接下来我通过一个例子来演示
大家就清楚了
现在我们通过这个例子来看看
首先我们将析构函数
不写成虚函数
大家看
这是基类的虚构函数
这是继承了Base类的Derived类
派生类的析构函数
都不是虚函数
然后我们故意来制造一个错误
大家看看
有可能发生什么样的错误
那我们来看
在这些析构函数里头
都做了什么事情
在这个Derived析构函数中
它去释放了
一个指针指向的空间
那么这个指针指向的空间
是哪儿来的呢
看看
它是在构造函数中
通过动态内存分配
分配了一个整数
然后把这个整数的起始地址
复给了它的数据成员p
比如我们看这个Derived类
它有一个指针成员
这是关键
而这个指针成员呢
它所指向的空间
是什么呢
是在构造函数中
动态分配的内存空间
也就是说
指针p所得到的这个地址
是由动态内存分配得到的
我们知道在构造函数中
如果做了这种动态内存分配
对应的在析构函数中
一般都是对应在析构函数中
要去释放它
也就是说
当这个对象要消亡的时候
因它的存在而占用的资源
就应该释放
如果不释放
那么就会造成资源泄漏
比如说
这个整数的内存单元如果不释放
那么就造成了内存泄漏
所以
当我们释放Derived对象的时候
要执行析构函数
必须要执行析构函数
是很重要的
那么大家想
一个对象当它消亡的时候
析构函数是必然被执行的呀
还会有什么情况
说漏掉了
没执行它的析构函数吗
还真有可能有这种情况
我们来看
这里我们定义一个函数fun
在这个函数的参数中呢
它接收一个基类指针
其实希望用这个函数
能够进行统一的对象的释放
好 我们在主函数中
通过new
去构造了一个Derived对象
然后将这个对象的地址
传给了fun函数
由fun函数去释放它
由于这个时候析构函数
它不是虚函数
所以编译器在编译阶段
就要决定释放
删除这个指针b
所指向的空间的时候
释放这个空间的时候
应该调用哪个对象的析构函数
在静态绑定的情况下
它只能知道b是基类指针
只能知道这一点点信息
所以它只知道要调用
Base类的析构函数
所以我们看
只有Base类的析构函数运行了
而Derived的析构函数呢
根本有没有被运行
这样的话
我们用new分配的空间
就没有被释放
所以这就会造成内存泄漏
怎么办呢
也很简单
我们在析构函数之前
都加上这个virtual就行了
把它定义成虚析构函数
定义成虚析构函数以后呢
进行同样地测试
在这个地方编译器
就不会在编译的时候去确定
该调用谁的析构函数了
就会等到运行时
根据它指针b
所指向的实际对象
确定该调用谁的析构函数
当然这次它的实际指向的对象
是Derived对象
虽然指针类型是Base类型
但是对象是Derived对象
那这样的话呢
就会去调用Derived的构造函数
就调用这个构造函数
所以我们看到
Derived destructor
这个输出显示在这儿
在这个构造函数的函数体中呢
就会释放动态分配的这个整数
它所占用的内存空间
释放这个空间
就把Derived对象
存续期间的资源释放了
当然
在每个派生类它的析构函数
执行完之后
紧接着会自动调用
它基类的析构函数
所以这个时候呢
Base类的析构函数
依然是会调用的
所以我们看到
Base destructor
在这儿显示
也就是进到这个函数里面了
所以我们在很多情况下
都是需要写虚析构函数的