2

Is it possible in C++ to overload in the child classes an overrided method? I'm asking this because I have many child classes that although they are the same (in my case game objects) they interact in different ways with each others.
So, I need to create a function like void processCollision(GameObject obj) in the superclass.
But that could be overloaded in the child classes depending on the class of the GameObject (if it's a building, a car ...).

I'm just trying to run from the alternative which is using upcasting and RTTI.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
Luis Alves
  • 1,286
  • 12
  • 32
  • Do you know the difference between overriding and overloading? Overrides have the same signature, overloads have different signatures. If you make your overloads virtual, then you can override them. – cup Oct 21 '14 at 17:59
  • 1
    You will probably get better feedback if instead of asking "Is X possible", you instead explain in detail what problem you're trying to solve and show what you've tried so far. – ApproachingDarknessFish Oct 21 '14 at 18:00
  • I added some more info. the problem is that I could overload the function processCollision, but if I do that I'll need to include the Gameobject child classes, and that will create a dependency cicle. – Luis Alves Oct 21 '14 at 18:01
  • 1
    If possible, please post some code that illustrates what you are trying to do. – R Sahu Oct 21 '14 at 18:34

2 Answers2

2

"I'm just trying to run from the alternative which is using upcasting and RTTI."

Virtual polymorphism doesn't need upcasting or RTTI. Usually that's what virtual member functions are for:

 class GameObject {
 public:
     virtual void processCollision(GameObject& obj);
 };

 class SomeGameObject1 : public GameObject {
 public:
     // SomeGameObject1's version of processCollision()
     virtual void processCollision(GameObject& obj) {
         // e.g here we also call the base class implementation
         GameObject::processCollision();

         // ... and add some additional operations
     }
 };

 class SomeGameObject2 : public GameObject {
 public:
     // SomeGameObject2's version of processCollision()
     virtual void processCollision(GameObject& obj) {
         // Here we leave the base class implementation aside and do something 
         // completely different ...
     }
 };

MORE ADDITIONS AND THOUGHTS

As you're mentioning upcasting I'd suspect you want to handle collisions differently, depending on the actual GameObject type passed. This indeed would require upcasting (and thus RTTI) like follows

class Building : public GameObject {
public:
     virtual void processCollision(GameObject& obj) {
          Car* car = dynamic_cast<Car*>(&obj);
          Airplane* airplane = dynamic_cast<Airplane*>(&obj);

          if(car) {
              car->crash();
          }
          else if(airplane) {
              airplane->crash();
              collapse();
          }

          void collapse(); 
     };

Based on the above, that makes me contemplative about some design/architectural principles:

  • May be it's not the best idea to place the processCollision() implementation strategy to the GameObject classes themselves. These shouldn't know about each other (otherwise it will be tedious to introduce new GameObject types to the model)
  • You should introduce a kind of GameManager class that keeps track of moving/colliding GameObject instances, and chooses a GameObjectCollisionStrategy class implementing void processCollision(GameObject& a,GameObject& b); based on the actual types of a and b.
  • For choosing the strategy, and resolve the final GameObject implementations and corresponding strategies, you should concentrate all of that business knowdlege to a CollisionStrategyFactory, and delegate to this.

The latter would look something like this

class GameObjectCollisionStrategy {
public:
    virtual processCollision(GameObject& a,GameObject& b) const = 0;
};

class CollideBuildingWithAirplane : public GameObjectCollisionStrategy {
public:
    virtual void processCollision(GameObject& a,GameObject& b) const {
         Building* building = dynamic_cast<Building*>(a);
         Airplane* airplane = dynamic_cast<Airplane*>(b);
         if(building && airplane) {
             airplane->crash();
             building->collapse();
         }
    }
};

class CollideBuildingWithCar : public GameObjectCollisionStrategy {
public:
    virtual void processCollision(GameObject& a,GameObject& b) const {
         Building* building = dynamic_cast<Building*>(a);
         Car* car = dynamic_cast<Car*>(b);
         if(building && car) {
             car->crash();
         }
    }
};

class CollisionStrategyFactory {
public:
    static const GameObjectCollisionStrategy& chooseStrategy
       (GameObject* a, GameObject* b) {
        if(dynamic_cast<Building*>(a)) {
            if(dynamic_cast<Airplane*>(b)) {
                return buildingAirplaneCollision;
            }
            else if(dynamic_cast<Car*>(b)) {
                return buildingCarCollision;
            }
        }
        return defaultCollisionStrategy;
    }

private:
    class DefaultCollisionStrategy : public GameObjectCollisionStrategy {
    public:
        virtual void processCollision(GameObject& a,GameObject& b) const {
           // Do nothing.
        }
    };

    // Known strategies
    static CollideBuildingWithAirplane buildingAirplaneCollision;
    static CollideBuildingWithCar buildingCarCollision;
    static DefaultCollisionStrategy defaultCollisionStrategy;
};

class GameManager {
public:
    void processFrame(std::vector<GameObject*> gameObjects) {
        for(std::vector<GameObject*>::iterator it1 = gameObjects.begin();
            it1 !=  gameObjects.end();
            ++it1) {
            for(std::vector<GameObject*>::iterator it2 = gameObjects.begin();
                it2 !=  gameObjects.end();
                ++it2) {
                if(*it1 == *it2) continue;
                if(*it1->collides(*it2)) {
                    const GameObjectCollisionStrategy& strategy = 
                        CollisionStrategyFactory::chooseStrategy(*it1,*it2); 
                    strategy->processCollision(*(*it1),*(*it2));
                }
            }         
        }
    }
};


Alternatively you may want to opt for static polymorphism, which also works without RTTI, but needs all types known at compile time. The basic pattern is the so called CRTP.

That should look as follows

 class GameObject {
 public:
     // Put all the common attributes here
     const Point& position() const;
     const Area& area() const;
     void move(const Vector& value);
 };

 template<class Derived>
 class GameObjectBase : public GameObject {
 public:
      void processCollision(GameObject obj) {
          static_cast<Derived*>(this)->processCollisionImpl(obj);
      }
 };


 class SomeGameObject1 : public GameObjectBase<SomeGameObject1 > {
 public:
     // SomeGameObject1's version of processCollisionImpl()
     void processCollisionImpl(GameObject obj) {
     }
 };

 class SomeGameObject2 : public GameObjectBase<SomeGameObject2 > {
 public:
     // SomeGameObject2's version of processCollisionImpl()
     void processCollisionImpl(GameObject obj) {
     }
 };

But this would unnecessarily complicate the design, and I doubt it will provide any benefits for your use case.

Community
  • 1
  • 1
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • But the processCollision is deferent depending on the Gameobject child class. (SomeGameObject1, SomeGameObject2 ...)´ – Luis Alves Oct 21 '14 at 18:04
  • 1
    @LuisAlves - `But the processCollision is deferent depending on the Gameobject child class` Exactly. That's the point of virtual functions. – PaulMcKenzie Oct 21 '14 at 18:05
  • Sorry, maybe I was not explicit, I would like to create the methods processCollision(SomeGameObject1 obj) and processCollision(SomeGameObject2 obj). I would like to do something like what the visitor does (upcasting an object using overloaded methods) but I can't since if I define the methods processCollision(SomeGameObject1 obj) and processCollision(SomeGameObject2 obj) in the GameObject, I'll have a circular dependency. – Luis Alves Oct 21 '14 at 18:06
  • 2
    @LuisAlves - `virtual void processCollision(GameObject& obj); ` Now there is no circular dependency if you `forward declare` GameObject. Note that a `reference` parameter is used. – PaulMcKenzie Oct 21 '14 at 18:09
  • @LuisAlves - is SomeGameObject actually derived from GameObject? If so, then there already is a dependency. – PaulMcKenzie Oct 21 '14 at 18:14
  • All SomeGameObject extend GameObject. (also they are all in separated files) – Luis Alves Oct 21 '14 at 18:16
  • @LuisAlves So you are including the header that defines `GameObject` within the header that defines `SomeGameObject`? That's the only way that `class SomeGameObject : public GameObject {` is going to compile without error. – PaulMcKenzie Oct 21 '14 at 18:19
  • Yes, the problem is that to have the methods processCollision(SomeGameObject1 obj) and processCollision(SomeGameObject2 obj), I'll need to include SomeGameObject1 and SomeGameObject2 headers in the GameObject. which will give me a circular dependency – Luis Alves Oct 21 '14 at 18:21
  • 1
    @LuisAlves _"Yes, the problem is that to have the methods processCollision(SomeGameObject1 obj) and processCollision(SomeGameObject2 obj)"_ Well, _overriding_ the base class method doesn't let you change the parameter type in the function signature. As you want something like ` processCollision(SomeGameObject1 obj)` you in fact need ` processCollision(GameObject& obj)` as signature in the base and derived class. But to determine if `GameObject&` actually is a `SomeGameObject1&` reference, you'll in fact need `dynamic_cast<>` and RTTI. – πάντα ῥεῖ Oct 21 '14 at 18:54
  • @LuisAlves You should take on R. Sahu's advice BTW, and illustrate your question with a concise code sample that reproduces those problems you claimed. – πάντα ῥεῖ Oct 21 '14 at 19:08
2

What you're trying to implement is normally called "multiple dispatch" and unfortunately C++ doesn't support it directly (because in C++ view methods are bounded with classes and there are no multimethods).

Any C++ solution will require some coding for the implementation.

One simple symmetric way to implement it is to create a map for the supported cases:

typedef void (*Handler)(Obj *a, Obj *b);
typedef std::map<std::pair<OType, OType>, Handler> HandlerMap;

HandlerMap collision_handlers;

then the collision handling is:

HandlerMap::iterator i =
    collision_handlers.find(std::make_pair(a->type, b->type));
if (i != collision_handlers.end()) i->second(a, b);

and the code goes in a free function.

If speed is a key factor and the object type can be coded in a small integer (e.g. 0...255) the dispatch could become for example:

collision_handlers[(a->type<<8)+b->type](a, b);

where collision handler is just an array of function pointers, and the speed should be equivalent to a single virtual dispatch.

The wikipedia link at the start of the answer lists another more sophisticated option for C++ (the visitor pattern).

6502
  • 112,025
  • 15
  • 165
  • 265
  • 1
    Another more complex solution to the problem of multiple dispatch in C++ is the Visitor Pattern. – YoungJohn Oct 21 '14 at 20:34
  • @YoungJohn: It's listed in the wikipedia article I linked, but I added an explicit reference to it anyway. Thanks. – 6502 Oct 21 '14 at 20:38
  • Do you intend `OType` to be something like `typeid(ConcreteGameObject)`? – πάντα ῥεῖ Oct 21 '14 at 21:51
  • @πάνταῥεῖ: I personally never found `typeid` very useful and I normally prefer either a regular data member or a virtual member returning an value if a data member size is a problem and the class has virtual methods anyway. Also for the second (fast) approach of double dispatch you need the class identification to be a small integer (directly usable as index) and `typeid` doesn't provide that. – 6502 Oct 22 '14 at 06:03