Reputation: 41
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
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
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
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