在C++中,有多继承关系,为什么要将析构函数定义成虚析函数
目录
1、在有继承关系中,为什么将析构函数定义为虚函数
在面试的时候,是否非常常被问到,在有继承关系中为什么要将析构函数定义为虚析函数,在此运用场景下,这是有特殊原因的。析构函数作为对象生命周期时最后一个所见的函数,我们在析构函数中做些清理工作或者内存释放、重置等工作。它犹如一个清洁工,自动打扫卫生,当然前提是你delete 这个对象或对象的生命周期结束。
2、问题解答
我们常常通过声明的基类对象指针访问接口,这样做得目的是为了代码的一致性和统一
如:
class Base
{
public virtual void update(){}
virtual ~Base(){}
}
class Monster: public Base
{
public virtual void update(){}
virtual ~Monster(){}
}
public Hero :public Base
{
public virtual void update(){}
virtual ~Hero(){}
}
void main()
{
vector<Base*> array;
array.add(new Monster());
array.add(new Hero ());
//通过统一的接口访问update函数
for(int i=0; i<array.size(); i++)
{
array[i]->update();
}
}
如上代码我们看到,往往我们需要通过数组或容器批量访问对象所指向类的函数,以求方便快捷
不然就只能这么写
Monster* pMonster = new Monster();
pMonster->update();
Hero * pHero = new Monster();
pHero ->update();
如果有千万个对象,那么就要写上几千万行如上代码,极其糟糕,维护起来也非常复杂。 所以在稍微复杂点的功能或系统中,我们需要统一的调用方式来实现代码一致性和简洁,将生成子类对象赋值给父类指针。
如:Base* p = new Monster();
但是这引发一个问题,我们知道new必须与delete 相对对应,空间生成与删除是一对欢喜冤家,new一个对象,必须一定要有一个delete相匹配; 当对象指针已经放入容器或作为参数传递时已经比较难于知道该指针所指向原来的所属那个类了(当然也可以知道,通过 dynamci_cast比对的方式,这又是个糟糕的主意)
如果我们不将析构函数声明成virtual函数时,在delete p的时候会怎么样呢?
class Base
{
public virtual void update(){}
~Base(){}
}
public Hero :public Base
{
private char* name;
public Hero()
{
name = new char[32];
memset(name,0,32);
}
public virtual void update(){}
//重置函数
private void reset(){}
//情况1
~Hero ()
{
delete[] name;
name = null;
reset();
}
//情况2
//virtual ~Hero ()
//{
//delete[] name;
//name = null;
reset();
//}
}
void main()
{
Base* p = new Hero();
/*
按照函数调用顺序原则(如果不知道的同学看下基础面试题:C++ 如何理解虚函数表
中的找到函数调用查找循序)
*/
delete p;
}
在情况1 delete p的时候,只会调用Base:~Base()析构函数,而不会调用Hero::~Hero*()析构函数,这样就会造成内存泄露,name的空间不会被释放掉,每new 出一个Hero对象,就内存泄露32个Byte内存空间。
在情况2 delete p的时候 会调用Hero::~Hero()析构函数。name的内存空间得以释放。
3、总结
由于析构函数往往做些清理工作,在有继承关系时,相关类的析构函数需要定义成虚析构函数,以防止清理工作出现遗漏等情况。当然可能产生内存泄露只是问题一部分,还有其他可能因为清理工作做得不够细致,变量值没有恢复或重置而照成访问数据不正确问题,这里就不做赘述。