首先,您应该注意到,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);
给定一个没有虚方法的简单类的方法指针can作为指向方法代码的常规指针来实现(注意:我正在使用vid
“Void of Int+Double”作为 C++ 编译器为处理重载而进行的“名称修改”的玩具示例(具有相同名称但不同参数的不同函数)。
然而,如果类具有虚方法,则情况就不再正确了。
大多数 C++ 编译器使用 VMT 实现虚拟调度...即
struct P2d {
...
virtual void doIt(int a, double b);
};
类似调用的代码p->doIt(10, 3.14)
where p
is a P2d *
与 C 编译器生成的结果相同
(p->$VMTab.vid$doIt)(p, 10, 3.14);
即实例包含一个指向虚拟方法表的隐藏指针,该虚拟方法表的每个成员都包含有效代码地址(假设编译器无法推断出该类p
确实是P2d
而不是派生方法,因为在这种情况下,调用可以与非虚拟方法相同)。
方法指针需要尊重虚拟方法......即调用doIt
间接使用派生实例上的方法指针P2d
需要调用派生版本,而相同的方法指针在使用时则需要调用基本版本P2d
实例。这意味着调用哪个代码的选择取决于指针和类实例。
一种可能的实现是使用蹦床:
void MethodPointerCallerForP2dDoit(P2d *p, int a, double b) {
p->doIt(a, b);
}
在这种情况下,方法指针仍然只是指向代码的指针(但指向蹦床,而不是最终方法)。
另一种方法是将方法指针存储为index相反,VMT 内部的方法。这是可行的,因为在 C++ 中,方法指针绑定到特定的类,因此编译器知道该类是否有虚拟方法。
多重继承不会使方法指针的事情变得复杂,因为所有内容都可以在编译时解析为单个最终 VMT 表。