我们知道对象在结束其生命周期之前,都会调用析构函数以完成必要的清理工作;派生类调用的析构函数顺序是“先子类,后基类”; 这篇文章用于总结当析构函数是普通析构函数、虚析构函数、纯虚析构函数时,我们使用delete运算符删除一个指针对象时,析构函数会有什么情况发生;
普通析构函数
CBase是基类,CDerive是其子类,类源码代码如下:
class CBase
{
public:
CBase(){}
//基类析构函数
~CBase(){ cout << "CBase Destructor" << endl; }
private:
int a;
};
class CDerive:public CBase
{
public:
CDerive(){}
//子类析构函数
~CDerive(){ cout << "CDerive Destructor" << endl; }
private:
int b;
};
测试代码如下:
//case1
CDerive *pDeriveObj = new CDerive();
delete pDeriveObj;
//case2
//基类指针对象可以指向派生类,体现基类和派生类赋值兼容关系(不同类型可以转化和赋值),
//但是pBaseObj只能访问基类成员,不能访问派生类成员
CBase* pBaseObj = new CDerive();
delete pBaseObj; //等价于删除基类对象
测试结果:
/* case 1
先析构子类:CDerive Destructor
后析构基类:CBase Destructor
*/
/* case2
仅析构基类:CBase Destructor
*/
总结:
1. 若delete运算符删除的是子类指针对象,则会调用子类和基类的析构函数;
2. 若析构函数是非虚的,即使基类指针指向的是子类对象,则delete 指针对象时,也仅调用基类的析构函数;
虚析构函数
当基类中的析构函数设置为虚函数时,我们在delete 基类指针对象时,能根据实际类型完成对象的清理工作;源码如下:
class CBase
{
public:
CBase(){}
//基类虚析构函数
virtual ~CBase(){ cout << "CBase Destructor" << endl; }
private:
int a;
};
class CDerive:public CBase
{
public:
CDerive(){}
//子类虚析构函数
virtual ~CDerive(){ cout << "CDerive Destructor" << endl; }
private:
int b;
};
测试代码:
//指向基类对象
CBase*pBaseObj_case1 = new CBase();
delete pBaseObj_case1;
//指向子类对象
CBase*pBaseObj_case2 = new CDerive();
delete pBaseObj_case2;
运行结果:
//case1
CBase Destructor
//case2
CDerive Destructor ->先子类
CBase Destructor ->后基类
总结:
当基类的析构函数为虚函数时,基类指针指向的是子类对象时,使用delete运算符删除指针对象,析构函能够按照“先子类,后基类”的原则完成对象清理;这样在多重继承的类中,能够保证每个类都能够得到正确的清理;比如基类和子类的缓冲区都能被释放;
纯虚析构函数
当基类中有纯虚函数时,基类是不能被实例化的,需要在子类中重写该纯虚函数;对于纯虚析构函数有点特殊,源码如下:
class CBase
{
public:
CBase(){}
//基类析构函数
virtual ~CBase()= 0;
private:
int a;
};
class CDerive:public CBase
{
public:
CDerive(){}
//子类析构函数
virtual ~CDerive(){ cout << "CDerive Destructor" << endl; }
private:
int b;
};
测试代码:
CBase*pBaseObj = new CDerive();
delete pBaseObj;
当我们编译代码时,会发现代码编译不过提示:
“error LNK2019: 无法解析的外部符号 “public: virtual __thiscall CBase::~CBase(void)” (??1CBase@@UAE@XZ),该符号在函数 “public: virtual __thiscall CDerive::~CDerive(void)” (??1CDerive@@UAE@XZ) 中被引用”
原因是子类析构时需要调用基类的析构函数,但发现代码中没有实现CBase析构函数,导致编译异常;
解决办法就是我们在CBase类外实现其函数体,而不是在子类中重写,而且~CDerive函数也是虚函数,即使其函数名不同;这就是和其他纯虚函数有特殊的地方;
我们需要在CBase类外增加如下的析构函数:
CBase::~CBase()
{
cout << "CBase Destructor" << endl;
}
这样得到的运行结果是:
CDerive Destructor ->先子类
CBase Destructor ->后基类
总结
最好把基类的析构函数声明为虚函数。这将使所有派生类的析构函数自动成为虚函数。这样,如果程序中显式地用了delete运算符准备删除一个对象,而delete运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数。
专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析构函数,以保证在撤销动态分配空间时能得到正确的处理。
参考资料: