「vtables は実装固有です」 (これはそうです) を超えて、vtable が使用されている場合: クラスごとに一意の vtables が存在します。B::fとC::fは仮想と宣言されていませんが、基本クラス (コード内の A )からの仮想メソッドに一致するシグネチャがあるため、 B::fとC::fは両方とも暗黙的に仮想です。 . 各クラスには少なくとも 1 つの一意の仮想メソッドがあるため ( B:: fはBインスタンスの A::fをオーバーライドし、 Cインスタンスの場合も同様にC:: f をオーバーライドします)、3 つの vtable が必要です。
通常、そのような詳細について心配する必要はありません。重要なのは、仮想ディスパッチがあるかどうかです。 呼び出す関数を明示的に指定することにより、仮想ディスパッチを使用する必要はありませんが、これは通常、仮想メソッドを実装する場合 (ベースのメソッドを呼び出す場合など) にのみ役立ちます。例:
struct B {
virtual void f() {}
virtual void g() {}
};
struct D : B {
virtual void f() { // would be implicitly virtual even if not declared virtual
B::f();
// do D-specific stuff
}
virtual void g() {}
};
int main() {
{
B b; b.g(); b.B::g(); // both call B::g
}
{
D d;
B& b = d;
b.g(); // calls D::g
b.B::g(); // calls B::g
b.D::g(); // not allowed
d.D::g(); // calls D::g
void (B::*p)() = &B::g;
(b.*p)(); // calls D::g
// calls through a function pointer always use virtual dispatch
// (if the pointed-to function is virtual)
}
return 0;
}
役立つかもしれないいくつかの具体的なルール。ただし、これらについて私を引用しないでください。いくつかのエッジケースを見逃している可能性があります。
- クラスに仮想メソッドまたは仮想ベースがある場合、継承されている場合でも、インスタンスには vtable ポインターが必要です。
- クラスが継承されていない仮想メソッドを宣言する場合 (基本クラスがない場合など)、独自の vtable が必要です。
- クラスに最初の基本クラスとは異なる一連のオーバーライド メソッドがある場合、そのクラスには独自の vtable が必要であり、基本の vtable を再利用することはできません。(デストラクタは通常これを必要とします。)
- クラスに複数の基底クラスがあり、2 番目以降の基底に仮想メソッドがある場合:
- 以前のベースに仮想メソッドがなく、空のベースの最適化がすべての以前のベースに適用された場合、このベースを最初のベース クラスとして扱います。
- それ以外の場合、クラスには独自の vtable が必要です。
- クラスに仮想基本クラスがある場合は、独自の vtable が必要です。
vtable はクラスの静的データ メンバーに似ており、インスタンスにはこれらへのポインターしかないことに注意してください。
Jan Gray による包括的な記事C++: Under the Hood (1994 年 3 月) も参照してください。(そのリンクが切れている場合は、Google を試してください。)
vtable を再利用する例:
struct B {
virtual void f();
};
struct D : B {
// does not override B::f
// does not have other virtuals of its own
void g(); // still might have its own non-virtuals
int n; // and data members
};
特に、Bの dtor は仮想ではないことに注意してください (これは実際のコードでは間違いである可能性があります)。ただし、この例では、DインスタンスはBインスタンスと同じ vtable を指します。