C++的行为规定是,
每个有虚函数的类,会有一张虚函数指针表,这张虚函数指针表指向最后实现的虚函数。
用指向派生类的基类指针调用函数时就会访问这张表。
从而体现了C++的多态技术,即指向派生类的基类指针可能有多种形态,
编译器并不在编译阶段决定这类指针指向的函数的具体位置,
而是在运行时通过查虚函数表的方式找到调用入口。
但是编译器也不是傻的,对于虚函数这一块可以做一些优化。
理论上所有基类指针所指向的虚函数都可以在编译阶段决定。
编译器并不需要每个都要等到运行的时候才去查表,对于一些直接调用编译器可以直接连接过去。
例如
A-》B(A中用一个基类指针调用了B)
所以对于这种情形我们并不用担心虚函数会影响性能。
但是对于一些间接的调用,则可能不得不查表了,否则会发生什么事呢?
我们看
A{1,N}-》B-》C,(A传了一个基类指针给B,然后B用这个指针调用了C。)
A{1,N}-》B的调用可以在编译阶段直接决定,但是B-》C就不好说,准确来讲这里有N种情况会发生,
如果B-》C的虚函数调用要再在编译阶段决定,那么一种最简单的做法就是,对B的代码做N份拷贝,
然后对调用C的地方做些修改就好了。
不使用虚函数表,这种最简单的情形,就使得编译后的机器码多了(N-1)份B的代码。
如果是A-》B-》C-》D,或者更复杂的情况呢,显然这种做法是不明智的。
提高代码的复用率,这也是虚函数存在的一个重要原因。
虽然带来一些性能上损失(查虚函数表),但是这个操作是常数复杂度的,
所以对于调用路径上有高复杂度的过程来说,这些损失完全可以忽略,
再加上编译器自身的优化,虚函数妥妥的。
虚函数除了提供多态机制外,对最终用户来说其他情形跟普通成员函数的行为一样。
也是被子类继承,也可以在子类指定namespace进行调用。
类的继承可以看做一个树结构,菱形也可以拆点变成树结构,最终的派生类是根部。
根据继承顺序,基类依次作为根部的叶子节点。对这棵树做先序遍历,
将每个基类定义的虚函数表,拿出来拼在一起,形成最终派生类的虚函数表。
更详细的内容见:
http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/