C++面向对象-26-虚析构和纯虚析构

这篇来学习多态中可能会发生内存泄漏和解决办法,就要使用到虚析构函数和纯虚析构函数。先不介绍概念,肯定和前面学构造函数和析构函数中的析构函数有关系。先通过引出问题,然后介绍这两个概念和特点。

 

1.多态基本代码

在前面例子我们可写出一下代码,也没有什么问题

#include 
#include 
using namespace std;

class Animal
{
public:
    virtual void speak() = 0;

};

class Cat : public Animal
{
public:
    virtual void speak()
    {
        cout << "小猫在说话" << endl;
    }

};


void test01()
{
   Animal * ani = new Cat;
   ani->speak();
   delete ani;
}

int main()
{
    test01();
    system("pause");
}

输出的内容是:小猫在说话。

 

2.子类添加成员属性,打印出构造和析构顺序

在子类Cat中,我们给添加一个成员属性,例如name,speak函数就打印  波斯猫在说话。然后补齐父类和子类中构造函数和析构函数中打印语句,方便测试观察调用顺序。

#include 
#include 
using namespace std;

class Animal
{
public:
    Animal()
    {
        cout << "Animal中调用构造函数" << endl;
    }

    virtual void speak() = 0;
    ~Animal()
    {
        cout << "Animal中调用析构函数" << endl;
    }
};

class Cat : public Animal
{
    
public:
    Cat(string name)
    {
        cout << "Cat中调用构造函数" << endl;
        m_Name = new string(name);
    }

    virtual void speak()
    {
        cout << *m_Name << "在说话" << endl;
    }
    string *m_Name;

    ~Cat()
    {
        if(m_Name != NULL)
        {
            cout << "Cat中调用析构函数" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }

};


void test01()
{
   Animal * ani = new Cat("波斯猫");
   ani->speak();
   delete ani;
}

int main()
{
    test01();
    system("pause");
}

上面这段代码是前面学习过的析构函数和构造函数,加上刚刚学纯虚函数。

前面学习继承的时候,学习过了构造函数和析构函数调用顺序。

关于构造函数,先调用父类的构造函数,然后调用子类的构造函数,可以这么记忆,现有老爹才有儿子。

关于析构函数,正好反过来,先调用子类析构函数,然后调用父类的析构函数。

 

运行这段代码

C++面向对象-26-虚析构和纯虚析构_第1张图片

看起来正常,认真一看,觉得少了什么。就是少了子类析构。

 

3.解决子类无法析构的问题

这个问题产生的原因是,我们在test01()函数中,第一行,父类指针指向子类对象。父类指针在析构的时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现了内存泄漏。

要解决这个问题很简单,就是把父类中析构函数改成虚析构。一个虚函数,只不过这里是构造函数类型的虚函数,叫虚析构函数。

#include 
#include 
using namespace std;

class Animal
{
public:
    Animal()
    {
        cout << "Animal中调用构造函数" << endl;
    }

    virtual void speak() = 0;
    virtual ~Animal()
    {
        cout << "Animal中调用析构函数" << endl;
    }
};

class Cat : public Animal
{
    
public:
    Cat(string name)
    {
        cout << "Cat中调用构造函数" << endl;
        m_Name = new string(name);
    }

    virtual void speak()
    {
        cout << *m_Name << "在说话" << endl;
    }
    string *m_Name;

    ~Cat()
    {
        if(m_Name != NULL)
        {
            cout << "Cat中调用析构函数" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }

};


void test01()
{
   Animal * ani = new Cat("波斯猫");
   ani->speak();
   delete ani;
}

int main()
{
    test01();
    system("pause");
}

在上面14行代码析构函数前面添加关键字virtual,运行

C++面向对象-26-虚析构和纯虚析构_第2张图片

利用虚析构可以解决:父类指针释放子类对象时不干净的问题

 

4.纯虚析构

在3的基础上,把父类中析构函数改成纯虚析构,也就是析构函数没有实现代码

C++面向对象-26-虚析构和纯虚析构_第3张图片

改完之后,其他代码不变,运行会报错。

C++面向对象-26-虚析构和纯虚析构_第4张图片

上面是一个链接的错误,这里纯虚析构 需要有声明也需要有实现。我们继续改代码如下

C++面向对象-26-虚析构和纯虚析构_第5张图片

改成纯虚析构函数后,Base类也变成一个抽象类,当然这里前面就有纯虚函数,本身就是抽象类。抽象类的特点就是不能被实例化。

 

总结:

多态使用时候,如果子类中有属性开辟到堆区(new),那么父类指针在释放时无法调用到子类的析构代码。
解决方法:将父类中的析构函数改成虚析构或者纯虚析构

虚析构和纯虚析构共性:
1)可解决父类指针释放子类对象
2)都需要有具体的函数实现


虚析构和纯虚析构区别:
如果纯虚析构,该类属于抽象类,无法实例化对象

 

你可能感兴趣的:(C++学习笔记,多态,c++,虚析构,纯虚析构)