0

したがって、基本クラスのポインターのリストがあります。

list<Base*> stuff;

次に、ある時点で、オブジェクトの 1 つが他のすべてのオブジェクトを調べます。

Base * obj = ...; // A pointer from the 'stuff'-list.
for (list<Base*>::iterator it = stuff.begin(); it != stuff.end(); it++)
{
    if (obj == *it)
        continue;

    // Problem scenario is here
   obj->interact(it);
}

私が達成したいのは、派生型objとが何であるかに応じて*it、それらが互いに異なる相互作用DerivedAをすることです。次のようなものです:DerivedBDerivedBbool c = true;

struct Base 
{
    virtual void interact(Base * b); // is always called
};
struct DerivedA : public Base 
{
    virtual void interact(Base * b){} // is never called
    virtual void interact(DerivedB * b) // is never called
    {
        if (b->c)
            delete this;
    }
};
struct DerivedB : public Base 
{
    bool c = false;
    virtual void interact(Base * b){} // is never called
    virtual void interact(DerivedA * a) // is never called
    {
        c = true;
    }
};
// and many many more Derived classes with many many more specific behaviors.

コンパイル時には、それらは両方ともBase-pointers であり、互いに呼び出すことができず、型が魔法のように現れることを期待できません。これが一方通行の関係である場合、つまり、そのうちの 1 つのタイプがわかっている場合は、Visitor パターンを使用できます。ある種のメディエーターパターンを使用する必要があると思いますが、メディエーターもポインターを保持するため、違いが生じないため、実際にどのように理解することができませんBase

続行する方法についての手がかりがありません... 誰か?


バックグラウンド:

私はゲームを作成しています。この問題は、ゲームの内容、つまり現在部屋にあるRoomものを追跡するクラスに起因しています。GameObject

オブジェクトが移動することがあります (たとえば、プレーヤー)。部屋は、すぐに移動される床タイル (上記のループ) にあるすべてのオブジェクトをループし、オブジェクトが互いに相互作用するかどうかを確認します。

たとえば、それTrollPlayer傷つけたい場合。または、彼は、別の「チーム」(関数からアクセスでき、すべてが実装されている)に由来するCharacterTrollPlayerから派生した)を傷つけたいだけです。CharactergetAlignment()Characters

4

6 に答える 6

4

可能であれば、「より効果的な C++」のコピーを入手し、基本的にここで探している複数のディスパッチの実装に関する項目 #31 を見てください。Meyers は、この問題に対するいくつかのアプローチと、それらのさまざまなトレードオフについて説明しています。(彼は例としてゲームを使用しています。)

ただし、おそらく彼が与える最善のアドバイスは、この機能を必要としないようにコードを再設計することです。テキストでは、非メンバー関数アプローチも検討されています。これには、相互作用を記述する各関数がどのオブジェクトに属するべきかという問題を排除するという追加のボーナスがあります。

于 2009-11-23T16:20:40.567 に答える
2

あなたの提案したアイデア ( を使用Base::interact) 関数はほぼ完成していると思います。唯一欠けている部分はこれのようです:

には、サブタイプのBaseすべてのオーバーロードが必要です 。構造interactに対するこの拡張を検討してください。Base

struct DerivedA;
struct DerivedB;
struct Base 
{
    virtual void interact(Base * b); // *<->Base interaction
    virtual void interact(DerivedA * da); // *<->DerivedA interaction
    virtual void interact(DerivedB * db); // *<->DerivedB interaction
};

これは、C++ で double-dispatch を実装する際の厄介な点です。新しいサブタイプを追加する場合、階層のベースに触れなければなりません。

于 2009-11-23T16:42:34.110 に答える
0

まず:なぜstruct代わりに使用するのclassですか?

class2番目:代わりに使用する場合は、次のstructようなことを行うことができます(必須)。

class Base 
{
    virtual void interact(Base * b); // see the VIRTUAL word (explained down)
}; 
class DerivedA : public Base 
{
    virtual void interact(DerivedB * b)
    {
        if (b->c)
            delete this;
    }
};
class DerivedB : public Base 
{
    bool c = false;
    virtual void interact(DerivedA * a)
    {
        c = true;
    }
};

キーワードを使用virtualすることはあなたが必要とするものです(私は推測します)。「ねえ!これはどこかでオーバーライドされているかもしれない」と言っているようにメソッドを定義する場合、virtualこれをコーディングするとき:

DerivedA* a = new DerivedA();
DerivedB* b = new DerivedB();
a.interact(b); // here instead of calling Base::interact(Base*) call the override method in DerivedA class (because is virtual)

編集:

その答えを忘れてください..(のコメントを見ませんでしたvirtual

編集2:

キャットウォークFrerichRaabeの回答をご覧ください。

于 2009-11-23T15:58:06.680 に答える
0

階層の最上位の仮想関数として相互作用する型のすべての可能な組み合わせを実装する必要があります。考えられるすべての相互作用をテストする例を次に示します。

#include<iostream>
void say(const char *s){std::cout<<s<<std::endl;}
struct DerivedA;
struct DerivedB;
struct Base{
  virtual void interact(Base *b) = 0;

  virtual void interactA(DerivedA *b) = 0;
  virtual void interactB(DerivedB *b) = 0;

};
struct DerivedA : public Base 
{
  virtual void interact(Base *b){
    b->interactA( this ); 
  }
  virtual void interactA(DerivedA *b){
     say("A:A");
  }
  virtual void interactB(DerivedB *b){
     say("A:B");
  }
};
struct DerivedB:public Base{
  virtual void interact(Base *b){
    b->interactB( this ); 
  }
  virtual void interactA(DerivedA *b){
     say("B:A");
  }
  virtual void interactB(DerivedB *b){
     say("B:B");
  }
};

void interact(Base *b1,Base *b2){
  b1->interact( b2 );
}
main(){
  Base *a = new DerivedA;
  Base *b = new DerivedB();

  interact(a,b);
  interact(b,a);
  interact(a,a);
  interact(b,b);
}
于 2009-11-23T16:42:45.903 に答える
0

あなたのinteract()関数は同じ署名を持っていません: 派生クラスでは、それらもある必要があります

virtual void interact(Base * b);

もちろんオプションですが、virtualわかりやすくするために入れておきます。

DerivedA::interact() がそのパラメーターで何かを行う必要があるかどうかを確認するには、基本クラスに別の仮想関数を実装できます。

virtual canDoX(Base * b);
virtual canDoY(Base * b);

次に、派生実装では次のようになります。

// DerivedA
void interact(Base * b)
{
    if (b->canDoX() && b->c)
        delete this;
}

// DerivedB
void interact(Base * b)
{
    if(b->canDoY())
        c = true;
}

更新: Frerich Raabe の回答が気に入ったので、私のアプローチの方が少し優れていると思う理由を説明させてください。
彼のアドバイスに従って、interact()ベースの派生クラスごとにメソッドを作成し、特定のクラスと対話できる他のすべての派生クラスを作成する必要があります。
私のソリューションでは、特定のプロパティのメソッドを追加する必要があり、それも組み合わせることができます。Troll を持っている場合はtrue、そのcanBeKilled()メソッドで返されます。リンゴ canBeEaten() とおいしい野生動物canBeKilled()、そしてcanBeEaten().
プロパティを組み合わせることができれば、追加する関数が少なくて済みます。

さらに、トロールが一定期間無敵になるエリクサーを飲んだ場合、トロールは戻っcanBeKilled() == falseてきて、それだけです. isInvulnerable()相互作用する各クラスでフラグをチェックする必要はありません。

于 2009-11-23T16:08:42.617 に答える
-2

あなたの問題は、次のような Scott Meyer の効果的な C++ でよく議論されていると思います。

規則 : 基本クラス ポインターを使用して派生クラス オブジェクトの配列にアクセスしようとしないでください --> 結果は未定義になります。

その例を挙げます:

構造体ベース{

virtual void print() { //ベース内 }

virtual ~Base() {} //

}

struct Derived : public Base {

virtual void print(){ //派生中 }

}

void foo(ベース *pBase,int N) {

for(int i=0; i<N ; i++)
   pBase[i].print(); // result will be undefined......

}

int main() {

 Derived d[5];
 foo(&d,5); 

}

そのような動作の理由は、コンパイラが sizeof(Base) バイトのジャンプで次の要素を見つけることです....

あなたは私の言いたいことを理解していると思います....

于 2009-11-23T16:30:23.073 に答える