面试题:基类的析构函数为何要声明为虚函数?
在 C++ 中,一个类的析构函数用于释放它的实例占用的资源。如果没有正确地释放这些资源,就可能会导致内存泄漏和其他严重的问题。基类的析构函数到底是否需要声明为虚函数取决于你是否会使用继承体系。
当使用继承时,如果一个基类指针指向了一个派生类对象,当对指针进行 delete 操作时,应该同时调用基类和派生类的析构函数。如果基类的析构函数不是虚拟函数,则运行时不能确认要调用哪个析构函数。如果它是虚拟的,则始终将调用最实际对象的析构函数。
考虑以下示例:
class Animal { public: Animal() { cout << "Animal constructor" << endl; } ~Animal() { cout << "Animal destructor" << endl; } }; class Dog : public Animal { public: Dog() { cout << "Dog constructor" << endl; } ~Dog() { cout << "Dog destructor" << endl; } }; int main() { Animal *p = new Dog(); delete p; return 0; }
在这个例子中,Dog 继承自 Animal 并实现了两个构造函数和两个析构函数。在 main() 函数中,通过基类指针创建一个 Dog 类型的对象,然后删除指针。由于 Animal 的析构函数不是虚拟的,所以仅调用了基类的析构函数:
Animal constructor Dog constructor Animal destructor
可以看到,Dog 类的析构函数没有被调用。如果将 Animal 的析构函数声明为虚拟,就会得到预期的结果:
class Animal { public: Animal() { cout << "Animal constructor" << endl; } virtual ~Animal() { cout << "Animal destructor" << endl; } }; class Dog : public Animal { public: Dog() { cout << "Dog constructor" << endl; } ~Dog() { cout << "Dog destructor" << endl; } }; int main() { Animal *p = new Dog(); delete p; return 0; }
输出结果:
Animal constructor Dog constructor Dog destructor Animal destructor
在这种情况下,派生类 Dog 的析构函数正常地被调用。
总结
在使用继承时,应该将基类的析构函数声明为虚函数,这样可以确保在运行时删除派生类对象时同时调用基类和派生类的析构函数。否则运行时不能确认要调用哪个析构函数,并且可能导致内存泄漏和其他问题。需要注意的是,每个具有虚函数的对象都包含一个指向虚函数表格(vtable)的指针,从而增加了内存开销,但是这种开销相对于可靠性和程序稳定性的提升来说是值得的。