这篇来学习多态中可能会发生内存泄漏和解决办法,就要使用到虚析构函数和纯虚析构函数。先不介绍概念,肯定和前面学构造函数和析构函数中的析构函数有关系。先通过引出问题,然后介绍这两个概念和特点。
1.多态基本代码
在前面例子我们可写出一下代码,也没有什么问题
#include <iostream>
#include <string>
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 <iostream>
#include <string>
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");
}
上面这段代码是前面学习过的析构函数和构造函数,加上刚刚学纯虚函数。
前面学习继承的时候,学习过了构造函数和析构函数调用顺序。
关于构造函数,先调用父类的构造函数,然后调用子类的构造函数,可以这么记忆,现有老爹才有儿子。
关于析构函数,正好反过来,先调用子类析构函数,然后调用父类的析构函数。
运行这段代码
看起来正常,认真一看,觉得少了什么。就是少了子类析构。
3.解决子类无法析构的问题
这个问题产生的原因是,我们在test01()函数中,第一行,父类指针指向子类对象。父类指针在析构的时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现了内存泄漏。
要解决这个问题很简单,就是把父类中析构函数改成虚析构。一个虚函数,只不过这里是构造函数类型的虚函数,叫虚析构函数。
#include <iostream>
#include <string>
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,运行
利用虚析构可以解决:父类指针释放子类对象时不干净的问题
4.纯虚析构
在3的基础上,把父类中析构函数改成纯虚析构,也就是析构函数没有实现代码
改完之后,其他代码不变,运行会报错。
上面是一个链接的错误,这里纯虚析构 需要有声明也需要有实现。我们继续改代码如下
改成纯虚析构函数后,Base类也变成一个抽象类,当然这里前面就有纯虚函数,本身就是抽象类。抽象类的特点就是不能被实例化。
总结:
多态使用时候,如果子类中有属性开辟到堆区(new),那么父类指针在释放时无法调用到子类的析构代码。
解决方法:将父类中的析构函数改成虚析构或者纯虚析构
虚析构和纯虚析构共性:
1)可解决父类指针释放子类对象
2)都需要有具体的函数实现
虚析构和纯虚析构区别:
如果纯虚析构,该类属于抽象类,无法实例化对象