派生对象之间的特殊交互(即多重调度)

发布于 2024-08-12 07:46:32 字数 1863 浏览 5 评论 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指针,并且无法相互调用并期望类型神奇地出现。如果这是一种单向关系,即我知道其中之一的类型,我可以使用 访客模式。我相信我应该使用某种 Mediator 模式 但无法真正弄清楚如何从中介者也将持有Base指针,因此不会产生任何影响。

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


背景:

我正在创建一个游戏,此问题源自 Room 类,该类跟踪其内容,即当前的 GameObject 是什么在房间里。

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

例如,如果它是一个巨魔玩家就会想要伤害它。或者他只是想伤害任何源自于任何其他“团队”(可以通过所有Characters 实现的函数getAlignment() 访问)。

So, I have a list of base class pointers:

list<Base*> stuff;

Then, at some point one of the objects will look through all other objects.

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);
}

What I want to achieve is that depending on what derived typeobj and *it are, they will interact differently with each other, i.e. DerivedA will destroy itself if it's interacting with DerivedB, but only if DerivedB has set the property bool c = true;. So something like:

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.

At compile time, they are both Base-pointers and will not be able to call each other and expect the type to magically appear. If this was a one way relation, i.e. I knew what type of one of them, I could use the Visitor pattern. I believe I should use some kind of Mediator pattern but can't really figure out how since the mediator too will hold Base-pointers and thus it won't make a difference.

I haven't got a clue on how to continue... anyone?


Background:

I'm creating a game, this problem originates from the Room class who keeps track of it's contents, i.e. what GameObjects are currently in the room.

Sometimes, an object moves (for example, the player). The room will then loop over all objects that are on the soon-to-be-moved-upon floor tile (the loop above) and will check if the objects will interact with eachother.

For example, if it's a Troll the Player would want to hurt it. Or he would just like to hurt any Character (both Troll and Player are derived from Character) that originates from any another "team" (which can be accessed from the function getAlignment(), which all Characters implement).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

留一抹残留的笑 2024-08-19 07:46:32

如果可以的话,请获取“更有效的 C++”的副本,并查看第 #31 项,它是关于实现多重分派的,这基本上就是您在这里寻找的内容。迈耶斯讨论了解决该问题的几种方法及其各种权衡。 (他甚至使用游戏作为示例。)

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

If you can, grab a copy of "More Effective C++", and have a look at item #31 which is about implementing multiple dispatch, which is basically what you're looking for here. Meyers discusses several approaches to the problem and their various trade-offs. (He even uses a game as an example.)

Perhaps the best advice he gives, however, is to try and redesign your code to avoid requiring this facility. In the text, a non-member function approach is also explored, which has the added bonus of eliminating the question of to which object each function describing an interaction should belong.

如若梦似彩虹 2024-08-19 07:46:32

我认为您建议的想法(使用 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++ 中实现双重调度是一件痛苦的事情:如果添加新的子类型,则必须触及层次结构的基础。

I think your suggested idea (with the Base::interact) function is almost complete. It seems that the only missing part is this:

In your Base, you need to have all the interact overloads for the sub-types. Consider this extension to your Base structure:

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
};

This is a painful thing about implementing double-dispatch in C++: if you add a new sub-type, you have to touch the base of the hierarchy.

濫情▎り 2024-08-19 07:46:32

您将需要将相互交互的类型的所有可能组合实现为层次结构顶部的虚拟函数。
这是一个测试所有可能的交互的示例:

#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);
}

you will need to implement all possible combinations of types interacting with each other as virtual functions on top of hierarchy.
Here's an example that tests all possible interactions:

#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);
}
一梦等七年七年为一梦 2024-08-19 07:46:32

第一:为什么使用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 关键字是你需要的(我猜)。如果您将一个方法定义为虚拟,您会告诉“嘿!这可能已在某个地方被覆盖”,所以..当您编写此代码时:

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)

编辑:

忘记那个答案..(没有看到虚拟

编辑2:

请参阅时装秀弗里希·拉贝 答案。

First: Why do you use struct instead of class?

Second: If you use class instead of struct you could (must) do something like this:

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;
    }
};

Using virtual keyword is wath you need (I guess). If you define a method as virtual you are telling "Hey! this maybe has been override someplace" so.. when you code this:

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)

EDIT:

Forget that answer.. (didn't see the comment of the virtual)

EDIT 2:

Please, see catwalk and Frerich Raabe answers.

因为看清所以看轻 2024-08-19 07:46:32

您的 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,它会在其 canBeKilled() 方法中返回 true。一个苹果 canBeEaten() 和一个美味的野生动物 canBeKilled(),然后是 canBeEaten()
如果可以组合这些属性,则必须添加更少的功能。

此外:如果巨魔喝了一些灵丹妙药,使其在一段时间内处于无敌状态,它会返回 canBeKilled() == false 就是这样。您不必检查每个其他交互类中的 isInvulnerable() 标志。

Your interact() functions don't have the same signature: In the derived classes they should also be

virtual void interact(Base * b);

The virtual is optional, of course, but for clarity I'd put it in there.

To find out whether DerivedA::interact() should do something with it's parameter, you can implement another virtual functions in your base class:

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

Then in the derived implementations it could look like this:

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

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

Update:
Since you liked Frerich Raabe's answer, let me explain why I think my approach is a bit better.
Following his advice, one has to create an interact() method for each derived class in the base and all other derived classes that can interact with a certain class.
With my solution one would have to add methods for certain properties, that can also be combined.
If you have a Troll it would return true in its canBeKilled() method. An apple canBeEaten() and a tasty wild animal canBeKilled() and then canBeEaten().
If you can combine the properties, you have to add fewer functions.

Furthermore: If the troll drank some elixir making it invulnerable for a period of time, it returns canBeKilled() == false and that's it. You don't have to check the isInvulnerable() flag in each other interacting class.

耀眼的星火 2024-08-19 07:46:32

我认为你的问题已经得到很好的讨论
Scott Meyer 的《Effective C++》如下:

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

我会给你一个例子:

struct Base{

virtual void print() { //in base }

virtual ~Base() {} //

}

struct Derived : public Base {

virtual void print(){ //in obliged }

}

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

我想你明白我的意思......

I think your problem is well discussed in
Scott Meyer's Effective C++ which is like as follows:

Rule : Don't try access array of derived class objects using base class pointer -->the result would be undefined.

I will give you an example of it:

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); 

}

The reason for it such behaviour is that compiler find next elements at the jump of sizeof(Base) bytes....

I think you got my point....

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文