c++析构函数、虚析构函数、纯虚析构函数详解

我们知道对象在结束其生命周期之前,都会调用析构函数以完成必要的清理工作;派生类调用的析构函数顺序是“先子类,后基类”; 这篇文章用于总结当析构函数是普通析构函数、虚析构函数、纯虚析构函数时,我们使用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运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数。

专业人员一般都习惯声明虚析构函数,即使基类并不需要析构函数,也显式地定义一个函数体为空的虚析构函数,以保证在撤销动态分配空间时能得到正确的处理。

参考资料:

http://c.biancheng.net/cpp/biancheng/view/247.html

http://blog.csdn.net/yapian8/article/details/46418687

posted @ 2017-07-08 16:38  小怪兽&奥特曼  阅读(847)  评论(0编辑  收藏  举报