1

私はこの質問を参照しました(タイトルを変更しました)。virtualnessに関連するコード生成は実装固有であることを認識しています。virtualただし、以前の質問は、非仮想ベースメソッドを呼び出す場合、継承に関連する追加コストがあることを示唆しています。

私は次のテストコードを書き、そのアセンブリをg ++(with -O4)でチェックしました:

共通部分

struct Base {
  int t_size;
  Base (int i) : t_size(i) {}
  virtual ~Base () {}
  int size () const { return t_size; };
};

struct D1 : virtual Base {
  int a[10];
  D1 () : Base(0) {}
  ~D1 () {}
};
struct D2 : virtual Base {
  int a[20];
  D2() : Base(0) {}
  ~D2 () {}
};

...

void foo (Base *p) 
{
  if(p->size())
    return;
  p = 0;
}

int main ()
{
  Derived d;
  foo(&d);
}

今、違いの部分はここにあります:

コード1(通常の継承)

struct Derived : Base {
  Derived () : Base(0) {}
  ~Derived () {}
  int a[100];
};

コード2(仮想継承)

struct Derived : virtual Base {
  Derived () : Base(0) {}
  ~Derived () {}
  int a[100];
};

コード3(複数の仮想継承)

struct Derived : D1, D2 {
  Derived () : Base(0) {}
  ~Derived () {}
  int a[100];
};

ここに全体的なコード

組み立てを確認したところ、3つのバージョンすべてに違いはありません。そして、以下はアセンブリコードです:

        .file   "virtualInheritFunctionCall.cpp"
        .text
        .p2align 4,,15
        .globl  _Z3fooP4Base
        .type   _Z3fooP4Base, @function
_Z3fooP4Base:
.LFB1:
        .cfi_startproc
        rep 
        ret 
        .cfi_endproc
.LFE1:
        .size   _Z3fooP4Base, .-_Z3fooP4Base
        .section        .text.startup,"ax",@progbits
        .p2align 4,,15
        .globl  main
        .type   main, @function
main:
.LFB2:
        .cfi_startproc
        xorl    %eax, %eax
        ret 
        .cfi_endproc
.LFE2:
        .size   main, .-main
        .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
        .section        .note.GNU-stack,"",@progbits

virtual特定の最適化がオンになっている場合、継承に追加のコストがかからないということですか?これを評価するために、もっと複雑なテストコードを実行する必要がありますか?最適化を行わないと、これらのアセンブリ間に違いが生じることに注意してください。

4

2 に答える 2

3

パフォーマンスよりも、仮想継承が非仮想ベース メソッドをどのように処理するかを知りたい

明らかに、thisポインタを調整またはクラス化し、それを元のメソッドに渡します。

3 番目の例を次のように調整すると、オーバーヘッドを観察できる場合があります。

  1. ベース、D1、および D2 に仮想メソッド (重複しない名前) を追加します。これにより、コンパイラは仮想メソッド テーブルを作成します。
  2. 重複しないデータ フィールド/メンバー変数 (重複しない、異なる名前) を Base、D1、D2、および派生に追加します。
  3. D2 と Base のデータ フィールドを操作する非仮想メソッドを D2 に追加します。
  4. D1 と Base のデータ フィールドを操作する非仮想メソッドを D1 に追加します。
  5. D2 および D1 で前述の非仮想メソッドを呼び出し、D2、D1、Base、および Derived のデータ フィールドを操作する非仮想メソッドを Derived に追加します。
  6. 分解調査。

また、メンバー変数がいくつかある場合は、結果の派生クラスのレイアウトをデバッガーで調査することをお勧めします。

アセンブリを確認したところ、3つのバージョンすべてに違いはありません

継承 (仮想かどうか) は、Derived* から Base* (またはthisベースの非仮想メソッドが派生メソッドから呼び出される場合) または vfptr に変換するときに、コンパイラがクラスへのポインターを調整することを決定する可能性があるという意味で、少し違いを追加する可能性があります。これにより、this関数/メソッドに渡す前に、またはポインターの現在の値に何らかの値が追加されます。

ただし、これは関数呼び出しが呼び出された時点で行われる可能性が高く、複数の継承が関係する場合にのみ発生する可能性が最も高くなります (複数の仮想メソッド テーブルが存在する可能性があるため)。つまり、クラスCを継承するクラスAを作成し、それらすべてに仮想メソッドがありますが、共通の祖先がない場合、 fromBに属するメソッドを呼び出すと、逆アセンブリでポインターの調整が表示される場合があります。しかし、それだけです。そのようなオーバーヘッドのコストは途方もなく小さいでしょう。AC

これはコンパイラ固有の質問であり、ここに書いたものはすべて Microsoft コンパイラの観察に基づいていることに注意してください。つまり、これは「文書化されていない機能」であるため、パフォーマンスが心配な場合は、パフォーマンスへの影響を推測するのではなく、プロファイラーを使用する必要があります。とにかくコードの可読性が最優先事項です。

于 2012-02-25T13:12:28.920 に答える
2

まず、次を見てくださいfoo

void foo (Base *p) 
{
  if(p->size())
    return;
  p = 0;
}

Base::size()は非仮想であるため、 による仮想ディスパッチのオーバーヘッドはありませんp->size()

次に、 を呼び出す方法を見てくださいfoo

int main ()
{
  Derived d;
  foo(&d);
}

ここでは、型が静的にわかっているインスタンスのアドレスを取得しています。つまり、 のインスタンスが与えられた場合Derived、コンパイラはそれを に変換する方法を静的に決定できBase *ます。したがって、 が からどのようにDerived継承されてBaseも、コンパイラはそれを変換する方法を知っています。

仮想継承の影響を測定するには、静的に利用できる型情報が少ない例が必要です。

于 2012-02-28T04:43:10.430 に答える