文章目录
- 一.基类析构函数未加virtual声明的情况
-
- 二.基类析构函数添加virtual声明的情况
- 三.总结
一.基类析构函数未加virtual声明的情况
在多态场景中,可通过基类的指针指向子类对象,并完成对子类对象的成员函数调用;在函数对象析构时,根据继承顺序,往往是先调用子类的析构函数,然后调用基类的析构函数,但是在多态场景中,可能会出现以下错误的情况:
1.1 基础示例演示
#include <iostream>
using namespace std;
class base
{
public:
base(){cout<<"基类构造"<<endl;};
~base(){cout<<"基类析构"<<endl;};
};
class obj:public base
{
public:
obj(){cout<<"子类构造"<<endl;};
~obj(){cout<<"子类析构"<<endl;};
};
int main()
{
base *pbase=new obj;
delete pbase;
pbase=nullptr;
return 0;
}
上述程序看似没问题,但是执行下来发现,输出结果如下:
可以发现此处在析构时仅仅调用了基类的析构函数,并没有调用子类的析构函数,在上述例子中并不会造成什么太坏影响,但是如果在子类的析构函数中做了内存释放、资源清楚等事情,上述情况就会造成内存泄漏,具体见下面的示例。
1.2 进阶示例演示
#include <iostream>
#include <string.h>
using namespace std;
class base
{
public:
base(){cout<<"基类构造"<<endl;};
~base(){cout<<"基类析构"<<endl;};
virtual void display(void) = 0;
};
class obj:public base
{
public:
obj(){
p = (char *)malloc(100);
strcpy(p,"子类申请的内存空间调用");
cout<<"子类构造"<<endl;
};
~obj(){
free(p);
p=nullptr;
cout<<"子类析构"<<endl;
};
virtual void display(void)
{
std::cout<<string(p)<<std::endl;
};
private:
char *p =nullptr;
};
int main()
{
base *pbase=new obj;
pbase->display();
delete pbase;
pbase=NULL;
return 0;
}
程序执行结果:
在上述示例中,可以发现在子类的构造函数中申请了100 byte的内存空间给指针p,在析构函数中释放了该内存,因此在执行子类构造函数后必须执行其析构函数,否则会造成内存泄漏,上述的执行结果显然没有调用子类的析构函数,必然造成p申请的内存空间得不到释放,造成内存泄漏,如何解决?基类析构函数需要加virtual声明。具体见下。
二.基类析构函数添加virtual声明的情况
如何解决在多态调用的场景中确保子类的对象能够被及时析构------在基类的析构函数加virtual声明。
#include <iostream>
#include <string.h>
using namespace std;
class base
{
public:
base(){cout<<"基类构造"<<endl;};
virtual ~base(){cout<<"基类析构"<<endl;};
virtual void display(void) = 0;
};
class obj:public base
{
public:
obj(){
p = (char *)malloc(100);
strcpy(p,"子类申请的内存空间调用");
cout<<"子类构造"<<endl;
};
~obj(){
free(p);
p=nullptr;
cout<<"子类析构"<<endl;
};
virtual void display(void)
{
std::cout<<string(p)<<std::endl;
};
private:
char *p =nullptr;
};
int main()
{
base *pbase=new obj;
pbase->display();
delete pbase;
pbase=NULL;
return 0;
}
程序执行结果:
上述示例中,在基类的析构函数声明时添加了virtual关键字,可见执行结果中调用了子类的析构函数,自然在子类析构函数中释放了p申请的内存,避免了内存泄漏。
三.总结
(1)基类的的析构函数不是虚函数的话,删除指针时,只有基类的内存被释放,派生类的没有,这样就内存泄漏了。
(2)析构函数不是虚函数的话,直接按指针类型调用该类型的析构函数代码,因为指针类型是基类,所以直接调用基类析构函数代码。
(3)当基类指针指向派生类的时候,如果析构函数不声明为虚函数,在析构的时候,不会调用派生类的析构函数,从而导致内存泄露。
(4)子类对象创建时先调用基类构造函数然后在调用子类构造函数,在清除对象时顺序相反,所以delete pbase只清除了基类,而子类没有清除。
(5) 什么时候才要用虚析构函数呢?通常情况下,程序员的经验是,当类中存在虚函数时要把析构函数写成virtual,因为类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时如果派生类的构造函数中有用new/malloc动态产生的内存,那么在其析构函数中务必要delete这个数据,但是一般的像以上这种程序,这种操作只调用了基类的析构函数,而标记成虚析构函数的话,系统会先调用派生类的析构函数,再调用基类本身的析构函数。
(6)一般情况下,在类中有指针成员的时候要写copy构造函数,赋值操作符重载和析构函数。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)