需要注意的是,本文的前提是实现多态
虚表是每个含有虚函数的类中维护的一张保存着各个虚函数地址的表
每个含有虚函数的类中都保存着一个指向虚表的指针,而虚表中保存了该类各个虚函数的地址。
而当子类对象过期时,需要被销毁,如果父类对象没有将析构函数声明为virtual,则在通过父类指针销毁子类对象时,只会调用父类析构函数,而子类对象比父类对象多出来的部分则不会被销毁,所以需要将父类析构函数声明为virtual。下面来个例子:
#include <iostream> #include <cstdlib> using namespace std; class Father { public: Father(){}; virtual ~Father(){}; }; class Son : public Father { public: Son() { cout << "Object created.\n"; } ~Son() { cout << "Son destructor called.\n"; } }; int main(void) { Father *p = new Son; delete p; system("pause"); return 0; }输出如下:
说明子类析构函数被调用了。
接着,我们把父类析构函数的virtual去掉,看会出现什么。
#include <iostream> #include <cstdlib> using namespace std; class Father { public: Father(){}; ~Father(){}; }; class Son : public Father { public: Son() { cout << "Object created.\n"; } ~Son() { cout << "Son destructor called.\n"; } }; int main(void) { Father *p = new Son; delete p; system("pause"); return 0; }输出如下:
通过这个实验说明,如果父类析构函数没有声明为virtual,则销毁子类时,子类对象的析构函数不会被调用,这会产生很严重的后果。
但是,如果没有涉及到内存分配,virtual也不是必须的。
另外需要说的是,使用指针访问对象的public虚函数成员时,程序是根据指向对象的类型来寻找对应的函数,而不是根据指针的类型来寻找对应的函数,这条规则对所有虚函数都成立,所以当子类被销毁时,父类指针指向的是子类对象,所以程序会找到子类的虚函数表,然后调用子类的析构函数,接着再调用父类的析构函数来释放父类分配的内存,这样才能使程序运行得井井有条。
而如果父类析构函数没有声明为virtual的话,那么上面的程序就会出错,因为父类、子类的析构函数都不在各自的虚函数表中,因为指针是父类类型的,所以会执行普通的直接调用public成员函数的方法来销毁对象。下面再来看个例子就明白了:
#include <iostream> #include <cstdlib> using namespace std; class Father { public: Father(){}; ~Father(){}; }; class Son : public Father { public: Son() { cout << "Object created.\n"; } ~Son() { cout << "Son destructor called.\n"; } }; int main(void) { cout << "Son1 "; Father *p1 = new Son; delete p1; cout << "Son2 "; Son *p2 = new Son; delete p2; system("pause"); return 0; }输出如下:
由此可见,要使用父类指针的形式实现多态,我们在大多数情况下必须将父类析构函数声明为虚函数。而少数情况下,比如在类中没有static变量、enum常量等且没有内存分配的情况下,可以不使用virtual,但是不管有没有,要实现多态,都建议对父类析构函数使用virtual,因为如今计算机的高速计算速度,完全可以忽略这方面带来的微小的效率影响。