クラスに仮想関数がある場合、このクラスのオブジェクトには vptr が必要です。これは、正しい仮想関数のアドレスが見つかる仮想テーブルである vtable へのポインタです。呼び出される関数は、オブジェクトが基本サブオブジェクトである最も派生したクラスである、オブジェクトの動的タイプに依存します。
派生クラスは基本クラスから実質的に継承するため、派生クラスに対する基本クラスの位置は固定されておらず、オブジェクトの動的な型にも依存します。gcc では、仮想基底クラスを持つクラスには、基底クラスを見つけるために vptr が必要です (仮想関数がない場合でも)。
また、基本クラスには、基本クラス vptr の直後に配置されるデータ メンバーが含まれます。基本クラスのメモリ レイアウトは { vptr, int
}です。
基本クラスに vptr が必要な場合、それから派生したクラスにも vptr が必要になりますが、多くの場合、基本クラスのサブオブジェクトの「最初の」vptr が再利用されます (再利用された vptr を持つこの基本クラスはプライマリ ベースと呼ばれます)。ただし、派生クラスは、仮想関数の呼び出し方法だけでなく、仮想ベースの場所を決定するために vptr を必要とするため、この場合は不可能です。派生クラスは、vptr を使用しないと仮想基本クラスを見つけることができません。仮想基本クラスがプライマリ ベースとして使用された場合、派生クラスは vptr を読み取るためにそのプライマリ ベースを見つける必要があり、そのプライマリ ベースを見つけるために vptr を読み取る必要があります。
したがって、派生物は基本ベースを持つことができず、独自の vptr を導入します。
したがって、型の基本クラス サブオブジェクトのレイアウトは次のようになります。、オフセットとして表されます。derived
int
base
型の完全なオブジェクトのレイアウトderived
は次のとおりです。derived
base
したがって、可能な最小サイズderived
は (2 int
+ 2 vptr) または一般的な ptr = int
= ワード アーキテクチャの 4 ワード、またはこの場合は 16 バイトです。(そして、Visual C++ はより大きなオブジェクトを作成します (仮想基本クラスが関与する場合)。私は、derived
ポインタがもう 1 つあると考えています。)
そうです、仮想関数にはコストがかかり、仮想継承にはコストがかかります。この場合の仮想継承のメモリ コストは、オブジェクトごとに 1 つ多くのポインターです。
多くの仮想基本クラスを含む設計では、オブジェクトあたりのメモリ コストは、仮想基本クラスの数に比例する場合もあれば、そうでない場合もあります。コストを見積もるには、特定のクラス階層について話し合う必要があります。
複数の継承や仮想基本クラス (さらには仮想関数) を持たない設計では、コンパイラによって自動的に行われる多くのことをエミュレートする必要があるかもしれません。一連のポインター、場合によっては関数へのポインター、場合によってはオフセット...紛らわしく、エラーが発生しやすい。