派生对象之间的特殊交互(即多个派遣)

时间:2009-11-23 15:32:23

标签: c++ design-patterns inheritance multiple-dispatch

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

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如果它与{进行交互则会自我毁灭{1}},但仅当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. - 指针,并且无法相互调用并期望该类型神奇地出现。如果这是单向关系,即我知道其中一种类型,我可以使用Visitor pattern。我相信我应该使用某种Mediator pattern,但无法弄清楚,因为调解员也会持有Base指针,因此它不会产生任何影响。

我还没有得到如何继续......任何人的线索?


背景

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

有时,物体移动(例如,玩家)。然后,房间将遍历即将被移动的地板砖(上面的环)上的所有物体,并将检查物体是否将彼此相互作用。

例如,如果它是GameObject Troll会想要伤害它。或者他只想伤害来自任何其他“团队”的任何PlayerCharacterTroll都来自Player)(可以从函数Character,所有getAlignment()都实现了。

6 个答案:

答案 0 :(得分:4)

如果可以,请抓住“更有效的C ++”的副本,并查看第31项关于实现多个调度的内容,这基本上就是您在这里寻找的内容。迈耶斯讨论了解决问题的几种方法及其各种权衡。 (他甚至以游戏为例。)

然而,他给出的最佳建议可能是尝试重新设计代码以避免需要此功能。在文本中,还探讨了非成员函数方法,其中还有一个额外的好处,即消除了描述交互的每个函数应该属于哪个对象的问题。

答案 1 :(得分: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 ++中实现双重调度是一件痛苦的事情:如果你添加一个新的子类型,你必须触及层次结构的基础。

答案 2 :(得分: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:

请参阅catwalkFrerich Raabe个答案。

答案 3 :(得分: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()标志。

答案 4 :(得分: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);
}

答案 5 :(得分:-2)

我认为您的问题已经过充分讨论了 Scott Meyer的Effective C ++如下所示:

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

我会举一个例子:

struct Base {

virtual void print(){// in base}

virtual~Base(){} //

}

struct Derived:public Base {

virtual void print(){// in derived}

}

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)字节跳转时找到下一个元素....

我认为你明白我的观点......