Per Ekström
Per Ekström

Reputation: 41

C++ - How to override child class methods using base pointers

So.

I am working on a small game project for school and I got collision and stuff working for my objects, and the idea is to check if an object collides with another and then have the specific logic for each type have a bunch of overloaded functions depending on the object being sent in. For instance, if the object is a player-controlled object, enemies will hurt it, but if it is a powerup colliding with an enemy, things will be fine.

So, I was hoping I could do something like (everything inherits from Obj obviously):

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

But I'm having trouble finding a way to send in the proper type. I made a test program demonstrating the problem:

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

And running it I get this:

C->A!
C->B!
C->A!
C->A!

When I was expecting:

C->A!
C->B!
C->A!
C->B!

Any idea how to make this work?

Upvotes: 2

Views: 119

Answers (3)

Edd
Edd

Reputation: 1370

The problem is that C++ uses single dispatch and what you want is double dispatch. One way to say this is that single dispatch uses the information available at compile time (you are passing a pointer of type A to a function) instead of info known at runtime (the pointer of type A happens to point to an object of type B and so you would like to call the overload for B) when deciding what overload of a function to call.

Some good resources on this: http://members.gamedev.net/sicrane/articles/dispatch.html and https://en.wikipedia.org/wiki/Double_dispatch

A way single dispatch programming languages usually go about solving this problem, without casting is using the "Visitor Pattern".

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

Note: traditionally Visitor class member classes are called visit(SomeVisitedClass &) and visited class member function is called receiver(Visitor &). But you can call it however you like.

Then you could do:

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

Now, while you're at it, you could also take a look at the Mediator Pattern. It is designed to alleviate the dependencies between classes that need to be aware of each others to work (like in your case) and save you from having to make each class aware of all the others:

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

Upvotes: 0

Christian Hackl
Christian Hackl

Reputation: 27538

What you are attempting here is called double dispatch - making a function virtual with respect to two arguments. C++, like most programming languages with virtual functions, does not support this natively.


Consider the c.doStuff(*base); call. It really has two arguments under the hood: c (which ends up as *this inside of the function) and *base.

Now the problem is that doStuff is virtual only with respect to the first argument. That's why the call would end up in one of C's doStuff functions even if c had a static base-class type. The second argument, however, is not handled in a polymorphic way. The static type of base is an A*, even it currently points to a subclass object, so *base yields an A, and that matches the A& overload.


While it's true that C++ does not support double dispatch, there are ways to simulate it. The Visitor Design Pattern is often cited as a way to do that. For example, see Difference betwen Visitor pattern & Double Dispatch.


P.S.: Mixing overriding with overloading is rarely a good idea, as it can become extremely confusing to find out, as a human reader of the code, which function will be called.

Upvotes: 3

Gambit
Gambit

Reputation: 1024

The problem is that your last call go doStuff you pass an A& so it will call C::doStuff(A&). If you want it to call C::doStuff(B&) you need to cast base to a B&. This can be done with static_cast and dynamic_cast.

You should look at Double Dispatch: Understanding double dispatch C++

Upvotes: 0

Related Questions