YouTube视频链接
C++的虚析构函数
本文是ChernoP68视频的学习笔记。
如下代码,创建两个类一个叫base作为基类,创建一个公共构造函数并打印constructor,这样就知道类被构造了,也就是构造函数被调用了。然后写一个析构函数并打印destructor,Derived类做同样的事并且派生自Base类。
我们创建Base类和Derived类的一个实例并把它们分配到堆上。
#include<iostream>
class Base
{
public:
Base() { std::cout << "Base Constructor\n"; }
~Base() { std::cout << "Base Destructor\n"; }
};
class Derived : public Base
{
public:
Derived() { std::cout << "Derived Constructor\n"; }
~Derived() { std::cout << "Derived Destructor\n"; }
};
int main()
{
Base* base = new Base();
delete base;
std::cout << "-----------------\n";
Derived* derived = new Derived();
delete derived;
std::cin.get();
}
按F5运行,我们发现当创建Base类的时候只会调用Base类的构造函数,删除它的时候也只会调用Base类的析构函数。而Derived类首先调用了基类Base的构造函数,然后是Derived类的构造函数 。当删除它的时候则是先调用Derived类的析构函数再调用Base类的析构函数。
若有一个多态类型poly,我们创建一个Derived实例,但把它赋值给Base类类型,把这种poly对象当作Base类指针来处理,但它实际上是一个指向Derived类型的指针。
int main()
{
Base* base = new Base();
delete base;
std::cout << "-----------------\n";
Derived* derived = new Derived();
delete derived;
std::cout << "-----------------\n";
Base* poly = new Derived();
delete poly;
std::cin.get();
}
按F5运行,发现基类构造函数和派生类构造函数都能被正确调用,然后当删除它的时候只有基类的析构函数被调用了,而派生类的析构函数没有被调用,这会导致内存泄漏。
当删除poly的时候,它不知道调用的析构函数可能有另一个析构函数,因为它没有被标记为虚函数。标记为virtual,意味着c++知道可能会有一个方法,在层次结构下的某种重写的方法。在普通的方法前面标记virtual,那么它就可以被覆写。而虚析构函数的意思不是覆写析构函数,而是加上一个析构函数。所以当把基类析构函数改为虚函数就会调用两个析构函数。它会先调用派生类析构函数,然后在层次结构中向上调用基类析构函数。
为什么要调用派生类析构函数?若有一个成员int数组在堆上分配东西,在构造函数中分配,在析构函数中删除。运行当前代码发现没有调用那个派生析构函数,但是它调用了派生类的构造函数。我们在这里分配了20个字节的内存,然后永远不会调用第14行代码,因为析构函数没有被调用,永远不会删除堆分配数组,这就是所谓的内存泄漏。
只需要将基类析构函数标记为虚函数就可以解决这个问题。意味着这个类可能被扩展为子类,可能会有一个析构函数也需要被调用。
我们发现派生类的析构函数首先被调用,然后调用基类的析构函数,这意味着数组得到了清理,即使当作多态类型来处理。