虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
-
可以解决父类指针释放子类对象
-
都需要有具体的函数实现
虚析构和纯虚析构区别:
-
如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
类名::~类名(){}
示例:
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; } ~Cat() { cout << "Cat析构函数调用!" << endl; if (this->m_Name != NULL) { delete m_Name;//清除指针指向的堆区数据 m_Name = NULL;//指针为空 } } public: string *m_Name; }; void test01() { Animal *animal = new Cat("Tom"); animal->Speak(); delete animal; } int main() { test01(); system("pause"); return 0; }
上述案例输出:
发现没有调用cat的析构函数,即堆区的内存没有被释放,内存泄漏。
问题产生原因:因为是用的父类的指针指向的子类对象Animal *animal = new Cat("Tom");
所以当用delete父类指针时不会走子类的析构,导致子类如果有堆区的数据会出现内存的泄露情况。
通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏 怎么解决?给基类增加一个虚析构函数 虚析构函数就是用来解决通过父类指针释放子类对象时不干净的问题
class Animal { public: Animal() { cout << "Animal 构造函数调用!" << endl; } virtual void Speak() = 0; //析构函数加上virtual关键字,变成虚析构函数 virtual ~Animal()//虚析构函数就是用来解决通过父类指针释放子类对象时不干净的问题 { cout << "Animal虚析构函数调用!" << endl; } };
若使用纯虚析构时也可以解决
若是直接改成纯虚析构会报错
class Animal { public: Animal() { cout << "Animal 构造函数调用!" << endl; } virtual void Speak() = 0; //析构函数加上virtual关键字,变成虚析构函数 virtual ~Animal() = 0; };
语法强制纯虚析构函数必须有函数实现,因为有时父类也有一些数据开辟在堆区,既要使用纯虚函数,又要释放父类在堆区中的数据,就需要使用类内纯虚函数声明,类外写实现的写法。
注意:区别于纯虚函数可以只写声明不写实现,纯虚析构需要声明也需要实现。有了纯虚析构后,这个类也属于抽象类,无法实例化对象。
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() { cout << "Cat析构函数调用!" << endl; if (this->m_Name != NULL) { delete m_Name;//清除指针指向的堆区数据 m_Name = NULL;//指针为空 } } public: string *m_Name; }; void test01() { Animal *animal = new Cat("Tom"); animal->Speak(); //通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏 //怎么解决?给基类增加一个虚析构函数 //虚析构函数就是用来解决通过父类指针释放子类对象 delete animal; } int main() { test01(); system("pause"); return 0; }
由于本案例在一些子类中有些数据开辟到堆区了,所以必须要走子类中的析构代码,如果使用了多态就走不到了,所以需要加上虚析构或者纯虚析构。
总结:
1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类