派生对象之间的特殊交互(即多重调度)
因此,我有一个基类指针列表:
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 GameObject
s 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
如果可以的话,请获取“更有效的 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.
我认为您建议的想法(使用
Base::interact
)功能已基本完成。似乎唯一缺少的部分是:在您的
Base
中,您需要拥有子类型的所有interact
重载。考虑对Base
结构的扩展:在 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 theinteract
overloads for the sub-types. Consider this extension to yourBase
structure: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.
您将需要将相互交互的类型的所有可能组合实现为层次结构顶部的虚拟函数。
这是一个测试所有可能的交互的示例:
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:
第一:为什么使用
struct
而不是class
?第二:如果你使用
class
而不是struct
你可以(必须)做这样的事情:使用
virtual
关键字是你需要的(我猜)。如果您将一个方法定义为虚拟
,您会告诉“嘿!这可能已在某个地方被覆盖”,所以..当您编写此代码时:编辑:
忘记那个答案..(没有看到
虚拟
)编辑2:
请参阅时装秀和弗里希·拉贝 答案。
First: Why do you use
struct
instead ofclass
?Second: If you use
class
instead ofstruct
you could (must) do something like this:Using
virtual
keyword is wath you need (I guess). If you define a method asvirtual
you are telling "Hey! this maybe has been override someplace" so.. when you code this:EDIT:
Forget that answer.. (didn't see the comment of the
virtual
)EDIT 2:
Please, see catwalk and Frerich Raabe answers.
您的
interact()
函数没有相同的签名:在派生类中,它们也应该是。virtual
当然是可选的,但为了清楚起见,我将它在那里。要确定 DerivedA::interact() 是否应该对其参数执行某些操作,您可以在基类中实现另一个虚拟函数:
然后在派生实现中,它可能如下所示:
更新:
既然您喜欢 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 beThe
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:
Then in the derived implementations it could look like this:
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 itscanBeKilled()
method. An apple canBeEaten() and a tasty wild animalcanBeKilled()
and thencanBeEaten()
.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 theisInvulnerable()
flag in each other interacting class.我认为你的问题已经得到很好的讨论
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)
{
}
int main()
{
}
这种行为的原因是编译器在 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)
{
}
int main()
{
}
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....