首先需要确认的是,编译器对非虚方法使用静态联编,对虚方法使用动态联编。
看起来,在大多数情况下,动态联编都更好,因为它让程序能够选择为特定类型设计的方法,这样问题就来了,既然动态联编这么好,为什么还要设计两种类型的联编?为什么默认的联编方法是静态的而不是动态?
原因关键就在于效率。Strousstrup说过(很经典,呵呵):C++的指导原则之一是,不要为不使用的特性付出代价(内存或处理时间)。
因为通常情况下,编译器处理虚函数的方法为:给每个对象添加一个隐藏成员,该成员中保存了一个指向函数地址数组的指针(称为虚函数表 virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类包含一个指针,指向了基类中所有虚函数的地址表,派生类对象将包含一个指向独立地址表的指针,如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址,如果没有重新定义,则保留原始版本的地址。调用虚函数时,程序将查看存储在对象中的vtbl地址,然后转向相应的函数地址表。
所以显而易见的是,使用虚函数时,在内存和执行速度方面有一定的额外成本,包括:
每个对象都将增大,增大量为存储地址的空间;对每个类,编译器都创建一个虚函数地址表(数组);每个函数调用都需要执行一步额外的操作,即到表中查找地址。
所以咱们要养成的习惯是,在设计类时,可能包含一些不在派生类重新定义的成员函数,那么这些函数就不要设置为虚函数。这样首先会有更好的效率,其次被声明为虚函数的成员函数就表明是预期在派生类中会被重新定义的,在阅读代码时也将比较方便。