0

所以,我有一个基类指针列表:

list<Base*> stuff;

然后,在某个时候,其中一个对象将查看所有其他对象。

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如果与 交互,则会破坏自身DerivedB,但前提是DerivedB设置了属性bool 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 并且不能互相调用并期望类型神奇地出现。如果这是一种单向关系,即我知道其中一种是什么类型,我可以使用访问者模式。我相信我应该使用某种Mediator 模式,但无法真正弄清楚如何因为 mediator 也将持有Base-pointers,因此它不会有所作为。

我不知道如何继续……有人吗?


背景:

我正在创建一个游戏,这个问题源于Room跟踪它的内容的类,即GameObject房间里当前有什么。

有时,一个物体会移动(例如,玩家)。然后房间将遍历即将移动的地砖上的所有对象(上面的循环),并检查对象是否会相互交互。

例如,如果它是一个Troll想要Player伤害它的人。或者他只是想伤害来自任何另一个“团队”(可以从所有实现的函数访问)的任何Character(两者TrollPlayer来自)。CharactergetAlignment()Characters

4

6 回答 6

4

如果可以,请获取“More Effective 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++ 中实现双重调度是一件很痛苦的事情:如果添加新的子类型,则必须触及层次结构的基础。

于 2009-11-23T16:42:34.110 回答
0

第一:你为什么使用struct而不是class

第二:如果你使用class而不是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:

请参阅时装表演Frerich Raabe 的答案。

于 2009-11-23T15:58:06.680 回答
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 的回答,让我解释一下为什么我认为我的方法更好一些。按照他的建议,必须为基类中的每个派生类以及可以与某个类交互的所有其他派生类
创建一个方法。 使用我的解决方案,必须为某些属性添加方法,这些方法也可以组合。如果你有一个 Troll,它会在它的方法中返回。一个苹果 canBeEaten() 和一个美味的野生动物然后. 如果可以组合属性,则必须添加更少的功能。interact()
truecanBeKilled()canBeKilled()canBeEaten()

此外:如果巨魔喝了一些长生不老药使其在一段时间内无懈可击,它就会回来canBeKilled() == false,仅此而已。您不必isInvulnerable()在其他交互类中检查标志。

于 2009-11-23T16:08:42.617 回答
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 回答
-2

我认为您的问题在 Scott Meyer 的 Effective C++ 中得到了很好的讨论,如下所示:

规则:不要尝试使用基类指针访问派生类对象的数组 --> 结果将是未定义的。

我会给你一个例子:

结构基{

virtual void print() { //in base }

虚拟 ~Base() {} //

}

结构派生:公共基础{

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

}

void foo(Base *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 回答