C ++ - 如何使用基指针覆盖子类方法

时间:2017-03-11 15:35:06

标签: c++ polymorphism

所以

我正在为学校开展一个小型游戏项目,我遇到碰撞,并且我的对象正在工作,我的想法是检查一个对象是否与另一个碰撞,然后让每个类型的特定逻辑都有一堆重载功能取决于发送的对象。例如,如果对象是玩家控制的对象,敌人会伤害它,但如果它是与敌人碰撞的通电,那么事情就会没事。

所以,我希望我可以做类似的事情(一切都从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!

知道如何使这项工作吗?

3 个答案:

答案 0 :(得分:3)

您在此处尝试的内容称为 double dispatch - 使函数虚拟化为两个参数。与大多数具有虚函数的编程语言一样,C ++本身并不支持这种语言。

考虑c.doStuff(*base);电话。它确实有两个参数:c(在函数内部最终为*this)和*base

现在问题是doStuff仅对第一个参数是虚拟的。这就是为什么即使C具有静态基类类型,调用也会以doStuff的{​​{1}}函数之一结束。但是,第二个参数不是以多态方式处理的。静态类型cbase,即使它当前指向子类对象,因此A*会生成*base,并且与A重载相匹配

虽然C ++不支持双重调度,但有一些方法可以模拟它。访客设计模式通常被认为是一种方法。例如,请参阅Difference betwen Visitor pattern & Double Dispatch

P.S。:混合覆盖与重载很少是一个好主意,因为作为代码的人类读者,找出哪个函数将被调用会变得非常混乱。

答案 1 :(得分:0)

问题是,您的上次通话doStuff传递了A&,因此会拨打C::doStuff(A&)。如果您想要拨打C::doStuff(B&),则需要将base投射到B&。这可以通过static_castdynamic_cast来完成。

你应该看看Double Dispatch:Understanding double dispatch C++

答案 2 :(得分:0)

问题是C ++使用单一调度,你想要的是双重调度。一种说法是单个调度使用编译时可用的信息(您将类型A的指针传递给函数)而不是运行时已知的信息(类型A的指针恰好指向类型B的对象)所以你想在决定要调用的函数的重载时调用B的重载。

有一些很好的资源:http://members.gamedev.net/sicrane/articles/dispatch.htmlhttps://en.wikipedia.org/wiki/Double_dispatch

单一调度编程语言通常用于解决此问题,无需投射正在使用&#34;访问者模式&#34;。

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&amp;),而访问类成员函数称为receiver(Visitor&amp;)。但是你可以随意调用它。

然后你可以这样做:

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

现在,当你了解它时,你也可以看一下Mediator Pattern。它旨在减轻需要了解彼此工作的类之间的依赖关系(如在您的情况下),并使您不必让每个类知道所有其他类:

https://en.wikipedia.org/wiki/Mediator_pattern