在最正常的情况下,你几乎可以想到
struct A {
int i;
int foo() { return i; }
};
A a;
a.foo();
作为
struct A {
int i;
};
int A_foo( A* this ) { return this->i; };
A a;
A_foo(&a);
(开始看起来像C
,对吗?)所以您会认为指针&A::foo
与普通函数指针相同。但是有一些复杂性:多重继承和虚函数。
所以想象我们有:
struct A {int a;};
struct B {int b;};
struct C : A, B {int c;};
它可能是这样布置的:
如您所见,如果您想用 anA*
或 a指向对象C*
,则指向起点,但如果您想用 a 指向它,B*
则必须指向中间的某个位置。
因此,如果C
从某个成员函数继承B
并且您想指向它然后在 a 上调用该函数C*
,则它需要知道对this
指针进行洗牌。该信息需要存储在某个地方。所以它与函数指针混为一谈。
现在,对于每个具有virtual
函数的类,编译器都会创建一个名为virtual table的列表。然后,它将指向该表的额外指针添加到类(vptr)。所以对于这个类结构:
struct A
{
int a;
virtual void foo(){};
};
struct B : A
{
int b;
virtual void foo(){};
virtual void bar(){};
};
编译器最终可能会变成这样:
所以指向虚函数的成员函数指针实际上需要成为虚表的索引。所以成员函数指针实际上需要 1) 可能是函数指针,2) 可能是this
指针的调整,以及 3) 可能是 vtable 索引。为了保持一致,每个成员函数指针都需要具备所有这些功能。所以这是8
指针的4
字节,调整的4
字节,索引的字节,16
字节总数。
我相信这实际上在编译器之间存在很大差异,并且有很多可能的优化。可能没有人真正按照我描述的方式实现它。
有关更多详细信息,请参阅此(滚动到“成员函数指针的实现”)。