4

Visual C ++は、実装が1つしかない純粋なクラスの関数を非仮想化しますか? 例えば:

class ICar
{
public:
virtual void Break() = 0;
};

class CarImpl : public ICar
{
public:
virtual void Break(){ .... }
};
4

2 に答える 2

6

OP の質問は、当然 3 つの質問に分かれます。

  1. VC++ 2010 は説明されている非仮想化を行いますか - 答えは: いいえ
  2. できたでしょうか - 答えは:はい、理論的には、状況によってはできたはずです。
  3. VC++ がそれをしないのはなぜですか。-推測しかできません...

詳細は次のとおりです。

1. そのような最適化は VC++ によって行われますか?

この最適化が行われていないことを証明するには、プロジェクト プロパティ/構成プロパティ/C/C++/出力ファイルでアセンブラー言語のリストを有効にする必要があります。

OPからわずかに変更されたC++コードを次に示します(ICarを抽象クラスから通常に変更しましたが、質問の要点は変わりません):

#include "stdafx.h"

class ICar
{
public:
    virtual void Accelerate(){printf("%s", "a\n");};
    virtual void Break(){printf("%s", "b\n");};
};

class CarImpl : public ICar
{
public:
    virtual void Accelerate(){ printf("%s", "accelerate\n"); }
    virtual void Break(){ printf("%s", "break\n"); }
    void Fly() { printf("%s", "fly\n"); }
};

int _tmain(int argc, _TCHAR* argv[])
{
    ICar *pCar = new CarImpl();
    pCar->Break();

    CarImpl *pCarImpl = new CarImpl();
    pCarImpl->Fly();

    CarImpl carImpl;
    carImpl.Break();
    carImpl.Fly();

    return 0;
}

まず、(注 1 )carImpl.Break();が仮想関数を使用していないことに注意してください。これは最適化の結果ではなく、C++ の機能です。コンパイル時にオブジェクトの型がわかっている場合、仮想関数のメカニズムは使用されません。仮想関数のメカニズムは、ポインターまたは参照が関係する場合にのみ使用されます。

次に、最適化 /O2 を有効にして、pCar->Break();(仮想メソッド) とpCarImpl->Fly();(非仮想メソッド) 用に生成されたアセンブラを確認します。

Break() の呼び出しについては、次のようになります。

; 24   :     pCar->Break();

    mov edx, DWORD PTR [eax]
    mov ecx, eax
    mov eax, DWORD PTR [edx+4]
    call    eax

EAX には CarImpl オブジェクトへのポインターが含まれています (ここに示されていないアセンブラーの前の行から明らかです)。最初の mov 命令では、CarImpl オブジェクトの最初の dword が EDX にロードされ (通常、オブジェクトの最初の dword は vtbl のアドレスです)、次にthisCarImpl の dword が ECX にロードされます (これは重要ではありません)。 EDX が指すポイント (仮想関数の表の 2 番目の関数) からのオフセット 4 が EAX にロードされ、呼び出しが完了します。

Fly() の場合、以下が表示されます。

; 27   :     pCarImpl->Fly();

    push    OFFSET ??_C@_04PPJAHJOB@fly?6?$AA@
    push    OFFSET ??_C@_02DKCKIIND@?$CFs?$AA@
    call    _printf

これは、2 つのパラメーターが渡された printf のインライン展開です。

そのため、Break() の場合、明らかに vtable の使用は最適化されていません。

2. そのような最適化は可能でしょうか

原則として、最適化できます。M.Ellis、B. Stroustrup、Addison-Wesley 1990 による "The Annotated C++ Reference Manual" で、次のステートメントを見つけました。 Stroustroup の正確な言い回しではないかもしれません。)

オブジェクトの正確なタイプがコンパイル時にわかっている場合、仮想関数のメカニズムは必要ありません。代わりに、実現により、クラス メンバー関数の通常の呼び出しを生成できます。(DK: コード内の carImpl.Break() の場合、私の注 1 を参照してください) ... 仮想関数がポインターまたは参照を介して呼び出される場合、オブジェクトの実際の型は静的に認識されない可能性があるため、仮想のメカニズム関数を使用する必要があります。制御フローについて十分な知識を持つコンパイラは、次のコードの bp を介した呼び出しのような場合でも、仮想関数への呼び出しをドロップできます。

struct base {
    virtual void vf1();
}
class derived : public base{
public:
    void vf1();
}

void g()
{
    derived d;
    base* bp = &d;
    bp->vf1();
}

... インライン仮想関数は完全に理にかなっていて、かなり頻繁に使用されます。当然、インライン化は、インライン関数が既知の型のオブジェクトに適用される場所でのみ使用されます。(DK: ここでは B. Stroustrup も carImpl.Break(); のケース、つまり NOTE1 で説明されているケースを参照していると思います)。

3. 実施しない理由

これはOPで文字通り尋ねられたわけではありませんが、おそらく隠された質問でした. Alex Cohn のコメントの 1 つに同意します (よく言われます)。

可能ですが、そうではありません。おそらく、このような呼び出しを確実に最適化するために必要なリソースを正当化するほど頻繁には発生しません。

于 2012-10-20T23:07:09.413 に答える
3

編集:この回答は、2012-10-20 の 2 番目の回答で廃止されました。コメントを保持するために削除しませんでした。

VC++ では、他の派生クラスをコンパイル済みの dll または exe モジュールにリンクできるため、これは不可能です。

于 2012-10-18T20:09:15.130 に答える