9

私はC++でゲームを書いています。このゲームには、それぞれわずかに異なる約30の異なる役割があります。すべてのロールに必要なすべてのデータを含むメインクラスUserがあります。私の最初の実装では、30個のロールを列挙して適切に処理するだけでしたが、Userを基本クラスとし、各ロールをUserから継承する独自のクラスにする方がよいのではないかと考えています。

私の主な関心事は、単一の基本クラスから継承する30以上のクラスがある場合に、ポリモーフィックメソッド呼び出しがどれほど効率的かということです。ポリモーフィック呼び出しには仮想テーブル内のポインターが含まれることは知っていますが、それがテーブル全体で正しいメソッドを線形検索することを意味するのか、それとも一定時間で実行できるのかはわかりません。

多くの継承されたクラスを使用したポリモーフィックメソッド呼び出しの効率について誰かがコメントできれば、私は啓蒙に感謝します。

前もって感謝します!

4

3 に答える 3

7

コストはごくわずかです。クラスの数や継承のレベルの数は関係ありません。ポリモーフィック呼び出しのコストは単純な加算vftableです。つまり、特定の関数のプラスへのポインター(標準では必須ではありませんが、ほとんどの場合、すべてではありませんが、これが正しい実装です)。

于 2012-04-15T23:31:12.387 に答える
7

継承を使用するのではなく、構成を使用します。'User'クラスにその役割を実行するオブジェクトを与えるだけです。したがって、30個の「ロール」クラスになってしまう可能性があります。ただし、これらは「User」を継承しませんが、使用するために「User」に与えられます(「Role」のインターフェースを定義するために独自の基本抽象クラスを継承する場合があります)

または、それが1つの関数である場合は、関数オブジェクトの束としてモデル化し、それらをUserに渡すことができます。

于 2012-04-15T23:32:48.807 に答える
0

残念ながら、LuchianGrigoreの答えは正しくありません。


ダイヤモンド継承の場合は正しいでしょう。Bから継承するクラスDとAから継承するCがある場合。Dの関数を呼び出すと、D参照を検索するための追加操作がvtableにあります。

 (e.g)                   in Memory
                          -------        Classes hierarchy
                     ---> |  A  |                A
the offset here      |    -------               / \
is the pointer   ->  |    | ... |              B   C    <- Diamond Inheritance
addition he is       |    -------              \   /        (usually very bad)
talking about        ---> |  D  |                D
                          -------

あなたの場合、ポリモーフィック呼び出しの場合、コンパイラは、呼び出している適切な関数を取得するために、vtableでルックアップ(読み取り)を実行する必要があります。さらに悪化するのは、vtableが指しているデータがキャッシュされていない場合です。その場合、キャッシュミスが発生しますが、これは非常にコストがかかります。

さらに、クラスごとに1つのvtableがあり、そのクラスの各オブジェクトは同じvtableを共有します。C ++では、vtableとは何ですか?どのように機能しますか?を参照してください。


あなたの質問に対する本当の答えは、それらの30のクラスすべての間で何回交換するか、そしてどれくらいの頻度でそれらを使用するかによって異なります。ループで使用されますか?

あなたが説明している解決策は、この潜在的な問題を回避するためによく使用されます。ただし、実際には、使用方法によっては、ポリモーフィック呼び出しと同じくらい高速になる場合があります。

したがって、一般的には、無視できるため、コストを気にせずにポリモーフィックメソッドを使用できます。最初にクリーンで保守可能なコードを記述し、後で最適化します。:)

編集 :

このスレッドで実際のコンパイラコードについての議論があったので、実際に何が起こっているのかを調べるためにサンプルを実行することにしました。

以下に、私が使用したコードのサンプルを示します。

#include <stdlib.h>
#include <stdio.h>

class I
{
public:
    virtual void f(void) = 0;
};

class A : public I
{
public:
    void f(void)
    {
        printf("A\n");
    }
};


int main(int argc, char* argv[])
{
    __asm
    {
        int 3
    }

    A* pA = new A();

    __asm
    {
        nop
        nop
    }

    pA->f();

    __asm
    {
        nop
        nop
    }

    A a;
    a.f();

    __asm
    {
        nop
        nop
    }

    return 0;
}

次に、サンプルの実際のアセンブリコード(コンパイラがどのように解釈したか)を確認できます。

int main(int argc, char* argv[])
{
    __asm
    {
        int 3
010E1010  int         3    
    }

    A* pA = new A();
010E1011  push        4    
010E1013  call        operator new (10E10A4h) 
010E1018  add         esp,4 
010E101B  test        eax,eax 
010E101D  je          main+17h (10E1027h) 
010E101F  mov         dword ptr [eax],offset A::`vftable' (10E2104h)
010E1025  jmp         main+19h (10E1029h) 
010E1027  xor         eax,eax 

    __asm
    {
        nop
010E1029  nop              
        nop
010E102A  nop              
    }

    pA->f();
010E102B  mov         edx,dword ptr [eax] 
010E102D  mov         ecx,eax 
010E102F  mov         eax,dword ptr [edx] 
010E1031  call        eax  

    __asm
    {
        nop
010E1033  nop              
        nop
010E1034  nop              
    }

    A a;
    a.f(); //Polymorphic call
010E1035  push        offset string "A\n" (10E20FCh) 
010E103A  call        dword ptr [__imp__printf (10E20ACh)]
010E1040  add         esp,4 

    __asm
    {
        nop
010E1043  nop              
        nop
010E1044  nop              
    }

    return 0;
010E1045  xor         eax,eax 
}

class A : public I
{
public:

    void f(void)
    {
        printf("A\n");
010E1000  push        offset string "A\n" (10E20FCh) 
010E1005  call        dword ptr [__imp__printf (10E20ACh)] 
010E100B  pop         ecx  
    }
于 2012-04-17T02:56:05.517 に答える