c++inheritancepolymorphismshared-ptrdouble-dispatch

Passing shared_ptr<Base> as shared_ptr<Derived>


I currently have the following structure

class A

class B : public A

class C : public A

I have virtual methods defined in A and B and C are overriding them. The methods are of the sort

bool C::CheckCollision(shared_ptr<B> box);

bool B::CheckCollision(shared_ptr<C> triangle);

I also have a vector of shared_ptr<A> in which I store all the game objects. The problem is that i cannot do the following

for (int i = 0; i < objects.size(); i++)
{
    for (int j=i; j < objects.size(); j++
    {
        objects[i]->CheckCollision(objects[j]);
    }

}

I get an error saying that the argument list does not match the overloaded function. Makes sense as I am trying to pass shared_ptr<A> where i am expecting shared_ptr<B> or shared_ptr<C>, but how do I go around this issue ? Is there a another way to do it ?


Solution

  • Let's make it work with virtual functions and shared pointer to base class

    First of all, you can perfectly make polymorphism work, using shared pointers to the base. Here a small snippet to show you how you could do it:

    class A {
    public: 
        virtual void show() { cout<<"A"<<endl; } 
        virtual void collide(shared_ptr<A> a) { cout<<"collide A with "; a->show();  } 
        virtual ~A() {}
    };
    
    class B : public A {
    public:
        void show() override { cout<<"B"<<endl; } 
        void collide(shared_ptr<A> a) override { cout<<"collide B with "; a->show();  } 
    };
    
    class C : public A {
    public:
        void show() override { cout<<"C"<<endl; } 
        void collide(shared_ptr<A> a) override { cout<<"collide C with "; a->show();  } 
    };
    

    Your double loop would then look like:

    vector<shared_ptr<A>> objects; 
    objects.push_back (make_shared<A>());   // populate for the sake of demo
    objects.push_back (make_shared<B>()); 
    objects.push_back (make_shared<C>()); 
    
    for (int i = 0; i < objects.size(); i++)
    {
        objects[i]->show(); 
        for (int j=i; j < objects.size(); j++)
        {
            objects[i]->collide(objects[j]);   // note that you have to use -> not .
        }
    }
    

    Now, you see, in order to master the combinations, I used an override that knows the real type of its own object but doesn't know anything specific about the real type of the partner object. So to work out which kind of pairing it is, I need to invoke a polymorphic function of the partner object.

    Online demo

    The more general approach to your problem is double dispatch

    This small proof of concept is to show a simple example. It's ideal when the problem can be decomposed in each of the partner object doing one part of the problem. But things are not always as simple, so you could find more elaborate techniques by googling for double dispatch. Fortunately the example of the collision is quite common.

    Here another demo that uses a combination of overriding and overloading. I think this is the kind of things you try to achieve but solves it through one level of indirection more. It is inspired by the visitor pattern: The polymorphic collision function of an object is invoked with a shared pointer to the base class of the partner object. But the implementation of this function immediately calls a polymorphic function of the partner object with as argument a reference to itself (i.e. the knowledge of the real type of the argument, allows the compiler to select the right overload). This kind of "bounce-back" approach (by the way, it's a kind of inverted visitor) unfortunately requires the base class to know all its potential derived class, which is far from ideal. But it allows to offer a different behavior for every possible combination.

    Another approach to double dispatch is to use a dispatch table. THis works by managing a kind of virtual table but with two types, and having some lookup for invoking the right function for the right combination.