当存在父类 Animal 子类 Cat
Animal* animal = new Cat("Tom");
animal->speak();
delete animal;
使用父类指针 指向在堆区上 创建的子类对象 此时animal的静态类型是Animal* 动态类型是 Cat* speak是虚函数 即speak函数的调用类型在运行时确定 可以调用Cat类中的speak函数
但在释放内存时 Cat类的析构函数却不会被调用
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat 析构函数调用" << endl;
delete m_Name;;
m_Name = NULL;
}
}
要解决这个问题(父类指针无法完全释放子类对象) 可以利用虚析构
逻辑详解:没有虚析构时 函数地址会在编译阶段直接绑定父类的析构函数 运行时只调用父类的析构函数 使用虚析构后 该析构函数及其派生类中的析构函数会存储到虚函数表中 按照编译器自动调用析构函数的顺序 先调用实际对象(派生类)的析构函数 再调用基类析构函数 从而避免内存泄漏
实现方法
1)虚函数 父类中的析构函数添加virtual关键字
virtual ~Animal()
{
cout << "Animal 虚析构函数调用" << endl;
}
2)纯虚函数 父类中声明纯虚函数
virtual ~Animal() = 0;
在类外提供实现
Animal:: ~Animal()
{
cout << "Animal 纯虚析构函数调用" << endl;
}
如果子类中没有堆区数据 可以不写虚析构和纯虚析构
纯虚析构 需要声明 也需要实现
有了纯虚析构后 这个类也属于抽象类 无法实例化对象
代码示例
#include <string>
#include <iostream>
using namespace std;
//虚析构和纯虚析构
class Animal
{
public:
Animal()
{
cout << "Animal 构造函数调用" << endl;
}
//纯虚函数
virtual void speak() = 0;
//利用虚析构可以解决 父类指针无法完全释放子类对象的问题
//如果子类中没有堆区数据 可以不写虚析构和纯虚析构
//纯虚析构 需要声明 也需要实现
//有了纯虚析构后 这个类也属于抽象类 无法实例化对象
virtual ~Animal() = 0;
};
Animal:: ~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;
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat 析构函数调用" << endl;
delete m_Name;;
m_Name = NULL;
}
}
string *m_Name;
};
//测试函数
void test01()
{
Animal* animal = new Cat("Tom");
animal->speak();
//父类指针在析构时 不会调用子类中的析构函数 导致子类中如果存在堆区属性 出现内存泄漏
delete animal;
}
int main()
{
test01();
system("pause");
return 0;
}