c ++中指向成员函数的指针分为三部分:
Offset Address/index virtual?
当使用调用派生对象时,偏移用于指针调整 base pointer.
这个偏移是如何实现的?它是指向某个表的指针,每个派生类有一个表,该表是否包含表单的条目(base X, offset)
?
另外,我在哪里可以获得更多关于此的信息?
首先,您应该注意到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)
where p
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); }
在这种情况下,方法指针仍然只是指向代码的指针(但对于蹦床,而不是最终方法).
另一种方法是将方法的索引存储在VMT内部作为方法指针.这是可行的,因为在C++中,方法指针与特定类相关联,因此编译器知道该类是否存在虚拟方法.
多重继承不会使方法指针复杂化,因为在编译时,所有内容都可以解析为单个最终VMT表.