3

このコードがあります:

#include <iostream>

class Base
{
   int x;
};

class Derived : virtual public Base
{
   int y;
};

int main()
{
    std::cout << sizeof(Derived) << std::endl; // prints 12
    return 0;   
}

一部のクラスが仮想的に継承されると、Derivedクラスの空のvtableが作成されることを読みました。したがって、メモリレイアウトは次のようになります。

Derived::ptr to empty vtable
Derived::y
Base::x

そしてそれは12バイトです。問題は、仮想メソッドがない場合、この空のvtableの目的は何であり、どのように使用されるのかということです。

4

3 に答える 3

5

DerivedBaseサブオブジェクトがどこにあるかを知るための何らかの方法が必要です。仮想継承では、基本クラスの相対的な場所は、派生クラスの場所に対して固定されていません。完全なオブジェクトのどこにでも配置できます。

ダイヤモンドの継承を含むより典型的な例を考えてみましょう。

struct A
{
    int a;
};

struct B1 : virtual A
{
    int b1;
};

struct B2 : virtual A
{
    int b2;
};

struct C : B1, B2
{
    int c;
};

ここでは、B1B2は事実上、から派生しAているため、には、サブオブジェクトCが1つだけあります。A両方ともB1、そのサブオブジェクトB2を見つける方法を知る必要があります(メンバー変数、またはそれらを定義する場合は 他のメンバーにアクセスできるようにするため)。AaA

これは、この場合にvtableが使用される目的です。両方ともB1、サブオブジェクトB2のオフセットを含むvtableを持ちます。A


上記のダイヤモンド継承の例を実装するためにコンパイラが何を行う可能性があるかを示すために、Visual C ++ 11DeveloperPreviewによって生成された次のクラスレイアウトと仮想テーブルについて考えてみます。

class A size(4):
        +---
 0      | a
        +---

class B1        size(12):
        +---
 0      | {vbptr}
 4      | b1
        +---
        +--- (virtual base A)
 8      | a
        +---

class B2        size(12):
        +---
 0      | {vbptr}
 4      | b2
        +---
        +--- (virtual base A)
 8      | a
        +---

class C size(24):
        +---
        | +--- (base class B1)
 0      | | {vbptr}
 4      | | b1
        | +---
        | +--- (base class B2)
 8      | | {vbptr}
12      | | b2
        | +---
16      | c
        +---
        +--- (virtual base A)
20      | a
        +---

および次のvtables:

B1::$vbtable@:
 0      | 0
 1      | 8 (B1d(B1+0)A)

B2::$vbtable@:
 0      | 0
 1      | 8 (B2d(B2+0)A)

C::$vbtable@B1@:
 0      | 0
 1      | 20 (Cd(B1+0)A)

C::$vbtable@B2@:
 0      | 0
 1      | 12 (Cd(B2+0)A)

オフセットはvtableのアドレスを基準にしていることに注意してください。また、とのサブオブジェクトに対して生成された2つのvtableB1B2場合C、オフセットが異なることに注意してください。

(これは完全に実装の詳細であることに注意してください。他のコンパイラは仮想関数とベースを異なる方法で実装する場合があります。この例は、それらが実装される1つの方法を示しており、非常に一般的にこの方法で実装されます。)

于 2012-01-28T20:04:36.663 に答える
1

仮想関数を実装するために、C++は仮想テーブルと呼ばれる特別な形式の遅延バインディングを使用します。仮想テーブルは、動的/遅延バインディング方式で関数呼び出しを解決するために使用される関数のルックアップテーブルです。仮想テーブルは、「vtable」、「virtual function table」、「virtual method table」、「dispatchtable」などの他の名前で呼ばれることもあります。

仮想テーブルは実際には非常に単純です。まず、仮想関数を使用する(または仮想関数を使用するクラスから派生した)すべてのクラスには、独自の仮想テーブルが与えられます。このテーブルは、コンパイラがコンパイル時に設定する単なる静的配列です。仮想テーブルには、クラスのオブジェクトから呼び出すことができる仮想関数ごとに1つのエントリが含まれています。このテーブルの各エントリは、そのクラスがアクセスできる最も派生した関数を指す関数ポインタです。

  • 仮想関数を使用する(または仮想関数を使用するクラスから派生した)すべてのクラスには、シークレットデータメンバーとして独自の仮想テーブルが与えられます。
  • このテーブルは、コンパイル時にコンパイラによって設定されます。
  • 仮想テーブルには、クラスのオブジェクトから呼び出すことができる各仮想関数の関数ポインターとして1つのエントリが含まれています。
  • 仮想テーブルは、純粋仮想関数へのNULLポインターを格納します。

仮想テーブルは、仮想基本クラスを持つクラスに対しても作成されます。この場合、vtableには、基本クラスの共有インスタンスへのポインターと、クラスの仮想関数へのポインター(存在する場合)があります。

于 2012-01-28T20:13:05.280 に答える
0

dynamic_cast <Derived *>(ptr_to_obj)を実行すると、vtableポインターを使用して、ptr_to_objがDerivedを参照しているかどうかが判別されます。仮想メソッドまたは継承に関係するすべてのクラスにはvtableが必要であり、dynamic_cast<>をサポートするにはクラスごとに区別する必要があります。メソッドへのポインタが含まれていない場合でも、オブジェクトのタイプを識別するために使用されます。

于 2012-01-28T20:03:55.720 に答える