最も通常の状況では、あなたはほとんど考えることができます
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;};
次のようにレイアウトされている可能性があります。
ご覧のとおり、オブジェクトをA*
またはでC*
ポイントする場合は開始点をポイントしますが、オブジェクトをポイントする場合B*
は中央のどこかをポイントする必要があります。
したがって、C
からいくつかのメンバー関数を継承しB
、それをポイントしてから関数を呼び出す場合は、ポインターC*
をシャッフルすることを知っている必要があります。this
その情報はどこかに保存する必要があります。そのため、関数ポインタにまとめられます。
これで、関数を持つすべてのクラスに対してvirtual
、コンパイラは仮想テーブルと呼ばれるそれらのリストを作成します。次に、このテーブルへの追加のポインタをクラス(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
バイト、合計バイト数です。
これは実際にはコンパイラ間で大きく異なるものであり、可能な最適化はたくさんあると思います。おそらく、私が説明したように実際に実装しているものはありません。
詳細については、これを参照してください(「メンバー関数ポインターの実装」までスクロールしてください)。