答案 0 :(得分:3)
Stanley Lippman的书"Inside the C++ Object Model"对这个主题进行了很好的介绍(尽管已过时但仍然有效)。
答案 1 :(得分:2)
虚拟是关于间接的。让我们开始简单:
struct Foo { void bar(int, bool) {} } x;
x.bar(12, false);
此处,Foo::bar()
对实例x
的调用在编译时是完全已知的并且是静态解析的:一个固定函数,它给出了实例引用和函数参数。功能名称,调用,完成。到目前为止没问题。
继续前进:
struct Boo { virtual void bar(char, float) = 0; };
extern Boo * foreign_function();
Boo * p = foreign_function();
p->bar('a', -1.5);
这次,在编译时无法知道bar()
调用应该去哪里。解决此问题的唯一方法是添加一个间接级别,允许您查找此成员函数的所有可能覆盖,并在运行时选择正确的覆盖,具体取决于*p
的动态类型。这次我们从函数名开始,在运行时执行查找,然后进行调用。这种模式应该仍然相当熟悉。
这里的要点是,足以知道*p
的动态类型是(非虚拟)基础Boo
的子类型,因此我们只需一次查找即可实现此类型(例如,指向与Boo
的表兼容的表的vtable指针。)
现在开始大鱼:
struct Voo { virtual void doo(double, void *) = 0; };
struct Left : virtual Voo { virtual void doo(double, void *); } };
struct Right : virtual Voo { virtual void doo(double, void *); } };
struct Most : Left, Right { virtual void doo(double, void *); } };
Left * p = /* address of a Most object, say */;
p->doo(0.1, nullptr);
我们已经知道我们不知道doo()
应该去哪里,我们必须在运行时查找它。但是,不再可能实现简单的一步间接。即使Left
是Voo
的子类而Right
也是Voo
的子类,Voo
的实际*p
基础子对象却不是实际上是Left
- 或Right
- 子对象的子对象 - (唯一!)虚拟子对象直接属于Most
(或者任何最派生的对象)。在实现方面,单个vtable指针并不好,因为我们不需要Left
的vpointer,也不需要Right
的vptr。相反,我们想要实际对象所具有的任何vpointer。
所以现在我们发现自己处于熟悉的状态:我们需要查看一些我们只能在运行时知道的东西。而这次我们需要查看的是实际的虚拟基础。所以过程如下:函数名称,在运行时查找虚拟基础,在虚拟基础中查找虚函数,然后进行调用。 (在虚拟化的典型vtable实现中,这通常通过“thunk”或“指针指针”进行额外的查找来完成。)
简而言之,“虚拟”意味着“在运行时确定”。
(这不会强制你的编译器生成运行时代码。如果在编译时可以证明调度的目标,那么调用可能是虚拟化的。但行为你的程序“好像”。)