当一个类作为基类时,我们往往需要为其指定一个虚析构函数,从而保证在删除指向子类对象的基类指针时能够调用子类的析构函数,避免内存泄漏。
比如以下的例子:
#include <iostream>
/* 猫基类 */
class Cat {
public:
virtual ~Cat() { std::cout << "~Cat()" << std::endl; };
};
/* 狸花猫 */
class DragonLi : public Cat {
public:
~DragonLi() { std::cout << "~DragonLi()" << std::endl; }
};
int main() {
Cat *dragonLi = new DragonLi();
delete dragonLi;
return 0;
}
其执行结果如下:
atreus@MacBook-Pro % clang++ main.cpp -o main
atreus@MacBook-Pro % ./main
~DragonLi()
~Cat()
atreus@MacBook-Pro %
但如果我们把基类析构函数中的 virtual 去掉,即:
/* 猫基类 */
class Cat {
public:
~Cat() { std::cout << "~Cat()" << std::endl; };
};
执行结果就会变为:
atreus@MacBook-Pro % clang++ main.cpp -o main
atreus@MacBook-Pro % ./main
~Cat()
atreus@MacBook-Pro %
可以看到,子类的析构函数并没有执行,这是因为这里的 delete
操作是静态联编的,编译器只会按照指针类型来执行对应的析构函数。
实际上,不管我们是否通过 virtual 对基类的构造或析构函数进行声明,子类都不能继承或着说重写父类的析构函数,但当基类通过 virtual 声明析构函数时,会触发动态绑定,形成父类析构函数和子类析构函数之间的多态,所以在析构时会先调用子类的析构函数(因为子类vptr指向自身的析构函数),然后再调用父类的析构函数(因为在实例化子类前先实例化了基类)。
这一点析构函数与普通函数是有着明显不同的,因为普通函数只有在存在函数重写时才会发生多态,但如果析构函数也可以重写,那么我们将无法释放基类中的成员。