首先您应该注意,C++ 方法可以像常规函数一样被实现(并且通常被实现),它在所有其他参数之前接受一个额外的隐藏参数,名为 this。
换句话说,在
struct P2d {
double x, y;
void doIt(int a, double b) {
...
}
};
doIt 的机器代码与 C 编译器生成的相同
void P2d$vid$doIt(P2d *this, int a, double b) {
...
}
像p->doIt(10, 3.14) 这样的调用被编译为P2d$vid$doIt(p, 10, 3.14);
给定一个没有虚方法的简单类的方法指针可以实现为指向方法代码的常规指针(注意:我使用 vid 表示“Void of Int +Double”作为 C++ 编译器用于处理重载的“名称修改”的玩具示例 - 具有相同名称但参数不同的不同函数)。
如果该类有虚方法,但事实并非如此。
大多数 C++ 编译器都实现了虚拟调度,不使用 VMT... 即在
struct P2d {
...
virtual void doIt(int a, double b);
};
像p->doIt(10, 3.14) 这样的调用代码,其中p 是P2d *,与C 编译器生成的代码相同
(p->$VMTab.vid$doIt)(p, 10, 3.14);
即该实例包含一个指向每个成员包含有效代码地址的虚拟方法表的隐藏指针(假设编译器无法推断 p 的类确实是 P2d 而不是派生的,因为在这种情况下调用可以与非虚拟方法相同)。
需要方法指针来尊重虚拟方法...即使用从P2d派生的实例上的方法指针间接调用doIt需要调用派生版本,而相同的方法指针是调用基在 P2d 实例上使用时的版本。这意味着选择调用哪个代码取决于指针和类实例。
一个可能的实现是使用蹦床:
void MethodPointerCallerForP2dDoit(P2d *p, int a, double b) {
p->doIt(a, b);
}
在这种情况下,方法指针仍然只是指向代码的指针(但指向蹦床,而不是指向最终方法)。
另一种方法是将方法的 index 作为方法指针存储在 VMT 内。这是可行的,因为在 C++ 中,方法指针与特定的类相关联,因此编译器知道该类是否存在虚方法。
多重继承不会使方法指针变得复杂,因为一切都可以在编译时解析为一个最终的 VMT 表。