C++ - 如何使用基指针覆盖子类方法
Posted
技术标签:
【中文标题】C++ - 如何使用基指针覆盖子类方法【英文标题】:C++ - How to override child class methods using base pointers 【发布时间】:2017-03-11 15:35:06 【问题描述】:所以。
我正在为学校做一个小型游戏项目,我的对象有碰撞和东西,我的想法是检查一个对象是否与另一个对象发生碰撞,然后让每种类型的特定逻辑都有一堆重载功能取决于发送的对象。例如,如果对象是玩家控制的对象,敌人会伤害它,但如果是与敌人碰撞的通电,一切都会好起来的。
所以,我希望我可以做类似的事情(显然一切都继承自 Obj):
std::vector<Obj*> list;
list.push_back(new Powerup());
list.push_back(new Enemy());
list.push_back(new Player());
for (auto i: list)
for (auto j: list)
if (collision(i,j))
i->doStuff(*j);
但是我很难找到发送正确类型的方法。我做了一个测试程序来演示这个问题:
#include <iostream>
class A
public:
virtual void doStuff(A& T) std::cout << "A->A!" << std::endl;
;
class B : public A
public:
virtual void doStuff(A& T) std::cout << "B->A!" << std::endl;
;
class C : public A
public:
virtual void doStuff(A& T) std::cout << "C->A!" << std::endl;
virtual void doStuff(B& T) std::cout << "C->B!" << std::endl;
;
int main()
A* base;
A a;
B b;
C c;
c.doStuff(a);
c.doStuff(b);
base = &a;
c.doStuff(*base);
base = &b;
c.doStuff(*base);
return 0;
运行它我得到了这个:
C->A!
C->B!
C->A!
C->A!
当我期待时:
C->A!
C->B!
C->A!
C->B!
知道如何进行这项工作吗?
【问题讨论】:
【参考方案1】:您在这里尝试的称为 双重调度 - 使函数相对于两个参数成为虚拟函数。与大多数具有虚函数的编程语言一样,C++ 本身并不支持这一点。
考虑c.doStuff(*base);
电话。它实际上有两个参数:c
(在函数内部以*this
结尾)和*base
。
现在的问题是doStuff
仅相对于第一个参数是虚拟的。这就是为什么即使c
具有静态基类类型,调用也会以C
的doStuff
函数之一结束。然而,第二个参数不是以多态方式处理的。 base
的静态类型是 A*
,即使它当前指向子类对象,所以 *base
产生 A
,并且与 A&
重载匹配。
虽然 C++ 确实不支持双重分派,但有一些方法可以模拟它。访问者设计模式经常被引用为一种方法。例如,请参阅Difference betwen Visitor pattern & Double Dispatch。
P.S.:将覆盖与重载混合并不是一个好主意,因为作为代码的人类读者,找出将调用哪个函数会变得非常混乱。
【讨论】:
【参考方案2】:问题是你最后一次调用doStuff
你传递了一个A&
所以它会调用C::doStuff(A&)
。如果您希望它调用C::doStuff(B&)
,您需要将base
转换为B&
。这可以通过static_cast
和dynamic_cast
完成。
你应该看看 Double Dispatch:Understanding double dispatch C++
【讨论】:
好的,所以我认为我必须这样做: void doStuff(A& T) A *a = dynamic_cast(T); B *b = dynamic_cast(T); if (a) std::cout A!" B!" 查看双重调度。如果您不想为每个派生类型编写强制转换,这就是您想要的。仅当您不知道 B 派生 A 时,动态转换才有用。如果 B 不派生 A,它将返回NULL
/nullptr
。这是一个缓慢的运行时操作。如果您知道 B 派生 A,请使用 static_cast
,否则您将获得 UB。【参考方案3】:
问题是 C++ 使用单调度,而您想要的是双调度。一种说法是,单次调度使用编译时可用的信息(您将 A 类型的指针传递给函数)而不是运行时已知的信息(A 类型的指针恰好指向 B 类型的对象因此,在决定要调用的函数的重载时,您想调用 B) 的重载。
一些很好的资源:@987654321@ 和 https://en.wikipedia.org/wiki/Double_dispatch
单一调度编程语言通常解决这个问题的一种方式,无需转换是使用“访问者模式”。
class DoStuffClass //visitor class
public:
void doStuff(B &b);//traditionally visit(B &b)
void doStuff(C &c);
void doStuff(A &a);
;
class A //visited class
public:
void interactWith(DoStuffClass &dsc)
//here's the good part:
//Now it is known at compile time(statically) what the type of *this is,
//so dispatching to the desired function - doStuff(A&) - can happen
doc.doStuff(*this);
;
class B: public A //visited class
public:
void interactWith(DoStuffClass &dsc)
//same as for A
doc.doStuff(*this);
;
注意:传统上Visitor类的成员类被称为visit(SomeVisitedClass &),被访问的类成员函数被称为receiver(Visitor &)。但是你可以随心所欲地称呼它。
那么你可以这样做:
DoStuffClass dsf;
A a;
B b;
A *aptr;
aptr = &b;
a.interactWith(dsf); //will call the do stuff function that handles A
b.interactWith(dsf); //will call the do stuff function that handles B
aptr->interactWith(dsf);//still calls the do stuff function that handles B
现在,当您使用它时,您还可以看看中介者模式。它旨在减轻需要相互了解才能工作的类之间的依赖关系(例如在您的情况下),并使您不必让每个类都了解所有其他类:
https://en.wikipedia.org/wiki/Mediator_pattern
【讨论】:
呃,在提交之前应该刷新页面:P Christian 已经提到了我所说的部分内容。不过,您可能会发现阅读第一个参考文献和最后一个参考文献(关于 Mediator 模式)很有趣。 感谢您的帮助,我设法使用此处的 3 个答案解决了它。 :) 很高兴听到队友。如果您发现此答案(和/或 Gambit 的)有用,Stack Overflow 会在答案旁边提供向上箭头以将其标记为有用。随意点击。 :)以上是关于C++ - 如何使用基指针覆盖子类方法的主要内容,如果未能解决你的问题,请参考以下文章