• 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

  • 相关阅读:
    java如何操作注册表(Preferences类)(在windows的注册表中保存、读取)
    转-正则表达式
    js数字格式化千分位格式
    es6严格模式需要注意的地方
    html5手势原理知识
    js事件总结
    js键盘相关知识总结
    html5 拖放
    js学习日记-隐式转换相关的坑及知识
    移动端各种分辨率匹配
  • 原文地址:https://www.cnblogs.com/jinxiang1224/p/8468244.html
Copyright © 2020-2023  润新知