构造函数和析构函数在C中意味着生命周期的开始和结束,它们的实现原理相同。由于析构函数往往还设置成虚函数,所以这里我重点介绍下C析构函数的原理和各种场景。
一、析构函数的作用
当对象的生命周期结束时,会自动调用析构函数,以清理一些资源,比如释放内存、关闭文件、关闭数据库连接等等。
二、析构函数调用的时机
(1)基类析构
我们反汇编下查看上面代码:
从反汇编中可以看出,在对象离开它的作用域时,编译器自动给我们添加了一个析构函数调用的语句。
那我们使用new产生的对象会什么时候调用析构函数呢,这里我们把fun1里对象改成动态生成。
void fun1()
{
Base *base = new Base();
cout<<“fun1 over”<<endl;
}
当我们不使用delete释放内存时,看反汇编的情况
此时,没有任何地方调用Base的析构函数
当我们使用delete释放对象时,
void fun1()
{
Base *base = new Base();
delete base;
cout<<“fun1 over”<<endl;
}
我们反汇编结果:
这里我们看到析构函数调用了,这是因为当我们使用delete删除对象时,编译器会自动在后面添加一条调用析构函数的语句;
从这里我们也可以看书,C中的new和delete和c语言中malloc和free的唯一区别就是使用new和delete编译会自动添加一条调用构造
函数和析构函数的语句,其他作用一样,都是释放内存,所以我们在C中一定要是否new和delete操作内存,不然就没办法调用构造或
析构函数了
(2)派生类析构
前面分析的是基类的析构函数,那派生类对象析构时自身和基类的析构函数什么时候调用呢,这里我们添加一个派生类:
从上面的分析我们可以知道,编译器会给我们添加一个派生类的析构函数调用,我们分析下派生类的析构函数
我们可以看出,派生类析构函数执行完后会执行基类的构造函数,这说明编译器给派生类析构函数后面都会增加一个直接父类的析构函数调用的语句,
这样就能保证派生类的所有父类的析构函数都会调用。
三、为什么要将基类的析构函数设置为虚函数
这里我们将派生类的对象赋值给基类的引用
这时候我们反汇编看析构函数的调用情况:
这里我们看到,编译器只给我添加了基类的析构函数调用,我们从上面分析指导,只有派生类的析构函数后面会添加父类的析构函数调用,而父类的析构函数
后面没有子类的析构函数调用,这就造成了派生类的析构函数没有调用,导致一些资源没有正常释放。
当我们把基类的析构函数设置为虚函数时,这时我们反汇编:
这个时候我们发现,编译器给我们添加调用的就不是一个具体的析构函数了,那我们调用的到底是哪个析构函数呢?
原来当我们将析构函数定义为虚函数的时候,编译器就会为每个对象添加一个虚表,保存着该对象实例类的虚函数指针(这里Base对象实例保存的就是Base类对应的析构函数指针,
Child对象实例保存的就是Child类的析构函数指针)。这时编译器就不会静态为我们生成调用那个具体的析构函数,而时在运行的时候查找这种虚表,找到对应的调用地址,因为
我们这里是Child对象实例,因此调用的就是Child类的析构函数,从而保证派生类和父类的析构函数都会调用。
四、什么时候要将析构函数设为虚函数
将所有类设为虚函数好吗?从上面可以看出,设为虚函数后,类的对象实例会增加一个虚表,占用额外的内存空间,而且调用的时候会查表再调用,对程序的性能影响不好;所以,
在真实的开发环境中,只需将那些实现多态的类(将子类对象指向父类引用)的基类设为虚函数就行了,这样可以避免程序性能变差。
原文链接:http://www.uptoday.net/articles/2017/12/09/1512825909082.html
来源:CSDN
作者:bingfeic
链接:https://blog.csdn.net/bingfeic/article/details/78761949