tags:

views:

217

answers:

2
#include<iostream.h>
#include<conio.h>

using namespace std;

class SpaceShip {};
class GiantSpaceShip : public SpaceShip {};

class Asteroid {
public:
  virtual void CollideWith(SpaceShip *) {
    cout << "Asteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(GiantSpaceShip *) {
    cout << "Asteroid hit a GiantSpaceShip" << endl;
  }
};

class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceShip *) {
    cout << "ExplodingAsteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(GiantSpaceShip *) {
    cout << "ExplodingAsteroid hit a GiantSpaceShip" << endl;
  }
};



int main()
{
  SpaceShip * s = new GiantSpaceShip();    
  Asteroid * a = new ExplodingAsteroid();
  a->CollideWith(s);  
  getch();  

 return 0;   
}

How can I enable double dispatch in C++?

+4  A: 

This is not single dispatch but double dispatch: you want the method to depend both on the actual/real type of the object it is invoked on, and on the actual/real type of the argument.

This issue can be solved by the Visitor design pattern.

Luc Touraille
@Luc: You are right. I edited my question after Neil pointed out the mistake
Bruce
Although double dispatch can be solved using a technique similar to that used in common implementations of the visitor pattern, I don't think it's correct to say that the visitor pattern itself solves double dispatch. In fact, I'd go the other way and say that double dispatch is a common way of implementing the visitor pattern.
Rob Kennedy
You are right that the main objective of the visitor pattern is not to implement the double dispatch, it is useful for adding functionality to a set of classes used polymorphically, without modifying them (at least not to much). However, it needs double dispatch to work (and hence proposes a clean way to implement it.)
Luc Touraille
+2  A: 

Luc is right on with using the Visitor pattern, I'm just expanding on that by giving an example of how you could do it.

#include <iostream>
#include <conio.h>

using namespace std;

class SpaceObject;
class SpaceShip;
class GiantSpaceShip;
class Asteroid;
class ExplodingAsteroid;

class SpaceObject {
public:
  virtual void CollideWith(SpaceObject*)       {}
  virtual void CollideWith(SpaceShip*)         {}
  virtual void CollideWith(GiantSpaceShip*)    {}
  virtual void CollideWith(Asteroid*)          {}
  virtual void CollideWith(ExplodingAsteroid*) {}
};



class Asteroid : public SpaceObject {
public:
  virtual void CollideWith(SpaceObject* o)       { o->CollideWith(this); }
  virtual void CollideWith(SpaceShip *)          { cout << "Asteroid hit a SpaceShip" << endl; }
  virtual void CollideWith(GiantSpaceShip *)     { cout << "Asteroid hit a GiantSpaceShip" << endl; }
};

class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceObject* o)       { o->CollideWith(this); }
  virtual void CollideWith(SpaceShip *)          { cout << "ExplodingAsteroid hit a SpaceShip" << endl; }
  virtual void CollideWith(GiantSpaceShip *)     { cout << "ExplodingAsteroid hit a GiantSpaceShip" << endl; }
};

class SpaceShip : public SpaceObject {
public:
  virtual void CollideWith(SpaceObject* o)       { o->CollideWith(this); }
  virtual void CollideWith(Asteroid* o)          { o->Asteroid::CollideWith(this); }
  virtual void CollideWith(ExplodingAsteroid* o) { o->ExplodingAsteroid::CollideWith(this); }
};

class GiantSpaceShip : public SpaceShip {
public:
  virtual void CollideWith(SpaceObject* o)       { o->CollideWith(this); }
  virtual void CollideWith(Asteroid* o)          { o->Asteroid::CollideWith(this); }
  virtual void CollideWith(ExplodingAsteroid* o) { o->ExplodingAsteroid::CollideWith(this); }
};

int main()
{
  SpaceObject* s = new GiantSpaceShip();
  SpaceObject* a = new ExplodingAsteroid();
  a->CollideWith(s);
  getch();

 return 0;
}
pdehaan
Can you explain why your example usage includes three dispatches instead of just two? (It's not called *triple dispatch*, after all.)
Rob Kennedy
Looks like pdehaan found a weakness of double dispatch. Where do you put the implementation? Since both objects are involved either class seems a valid location. Personally, I put the implementation in the heavier class even it creates 3 calls (2 for double dispatch and a 3rd to forward the invocation to the preferred class). My 3rd call is not virtual, but a direct call to minimize the overhead. If both classes are equally weighty I have left the implementations as free friend functions, though I've never needed to do this in production code.
caspin
@Rob: Are you referring to the main call resulting in three calls before resolving, or the fact that I define CollideWith 3 times per sub-class?Passing 'this' as an argument passes as the type 'this' is when the function is defined. If I don't define the method inside each class, it reverts to the parent version, which only passes itself as the parent type.With each class having methods for both the base type and specific types they care about, I can make any two classes interact without knowing exactly which class they are.There's probably better ways for specific cases, but this works.
pdehaan
@pdehaan, I edited your answer to remove the extra dynamic calls.
caspin
I'm referring to the former — *three* calls to `CollideWith` occur before there's any real work. Ideally, the work would occur in the second call. Caspin's edit reduced the effect a little, but there are still three `CollideWith` calls before anything really happens. The reason for this oddity is threefold: the asteroids and the space ships all descend from the same class, that class is allowed to collide with itself, and either object can initiate the collision. If you'd called `s->CollideWith(a)`, then there'd be just two dispatches because the asteroid classes do the work.
Rob Kennedy
If you can guarantee one of the objects is going to be of a particular class every time, optimize for that situation. I just went with the assumption that asteroids and ships might all be stored together and expected to possibly interact as well.Alternately, you could duplicate the meaningful code in all relevant classes. This would ensure only two calls either way, but add more code.
pdehaan