Matthew
Matthew

Reputation: 23

Strange behavior on overriding methods and calling them with shared_ptr

For my collision code, I thought it would be neat if each entity overrode and defined its own interaction with any other entity. So, I tried to implement this:

Entity.h:

class Bullet;
class Person;
class Entity
{
public:
    Entity();
    ~Entity();
    virtual void resolveCollision(Entity &other);
    virtual void resolveCollision(Bullet &other);
    virtual void resolveCollision(Person &other);
};

Entity.cpp:

void Entity::Entity() {}
void Entity::~Entity() {}
void Entity::resolveCollision(Entity &other) {
    std::cout << "collided entity with entity" << std::endl;
}
void Entity::resolveCollision(Bullet &other) {
    std::cout << "collided entity with bullet" << std::endl;
}
void Entity::resolveCollision(Person &other) {
    std::cout << "collided entity with person" << std::endl;
}

Person.h:

#include "Entity.h"
class Person :
    public Entity
{
public:
    Person();
    ~Person();
    void resolveCollision(Entity &other) override;
    void resolveCollision(Bullet &other) override;
    void resolveCollision(Person &other) override;
};

Person.cpp:

Person::Person() {}
Person::~Person() {}

void Person::resolveCollision(Entity &other) {
    std::cout << "collided person with entity" << std::endl;
}
void Person::resolveCollision(Bullet &other) {
    std::cout << "collided person with bullet" << std::endl;
}
void Person::resolveCollision(Person &other) {
    std::cout << "collided person with person" << std::endl;
}

Bullet.h (almost a replica of Person.h):

#include "Entity.h"
class Bullet :
    public Entity
{
public:
    Bullet();
    ~Bullet();
    void resolveCollision(Entity &other) override;
    void resolveCollision(Bullet &other) override;
    void resolveCollision(Person &other) override;
};

Bullet.cpp (almost a replica of Person.cpp):

Bullet::Bullet() {}
Bullet::~Bullet() {}

void Bullet::resolveCollision(Entity &other) {
    std::cout << "collided bullet with entity" << std::endl;
}
void Bullet::resolveCollision(Bullet &other) {
    std::cout << "collided bullet with bullet" << std::endl;
}
void Bullet::resolveCollision(Person &other) {
    std::cout << "collided bullet with person" << std::endl;
}

Finally, the main.cpp:

#include "Bullet.h"
#include "Person.h"
#include <typeinfo>

int main()
{
    std::vector<std::shared_ptr<Entity>> entities;
    entities.push_back(std::shared_ptr<Person>(new Person()));
    entities.push_back(std::shared_ptr<Bullet>(new Bullet()));
    std::cout << typeid(entities[0]).name() << std::endl;
    std::cout << typeid(*entities[0]).name() << std::endl;
    std::cout << typeid(entities[1]).name() << std::endl;
    std::cout << typeid(*entities[1]).name() << std::endl;
    (*entities[0]).resolveCollision(*entities[1]);
    Person().resolveCollision(Bullet());
    return 0;
}

For some odd reason, the console outputs the following:

class std::shared_ptr<class Entity>
class Person
class std::shared_ptr<class Entity>
class Bullet
collided person with entity
collided person with bullet

So it seems to be recognizing that *entities[1] is a class Bullet, but for some reason, it calls the Person::resolveCollision(Entity) instead of Person::resolveCollision(Bullet) even though making instances of those classes and doing the same thing outputs a collision between a player and bullet. What am I doing to cause this? Does the forward declaration cause this?

Thank you!

Upvotes: 2

Views: 300

Answers (2)

Abdus Khazi
Abdus Khazi

Reputation: 473

In order to get the desired behaviour use the following piece of code :

auto bulletPtr = std::dynamic_pointer_cast<Bullet>(entities[1]);
if(bulletPtr)
{
    entities[0]->resolveCollision(bulletPtr);
}

entities[0]'s resolvecollision will call Person's resolveCollision as the function is virtual and overridden in the derived class. The c++ run-time has no clue that entities[1] is actually a bulletPtr, so you have to dyamically cast it down. You have to check for the validity of pointer returned by dynamic_pointer_cast.

Upvotes: 0

R Sahu
R Sahu

Reputation: 206567

The call

(*entities[0]).resolveCollision(*entities[1]);

resolves to Entity::resolveCollision(Entity &other); at compile time. By virtue of virtual function dispatch mechanism, the call is dispatched to Person::resolveCollision(Entity &other); at run time. However, the dynamic dispatch system does not change the call to Person::resolveCollision(Bullet &other); based on the run time information of other. That would require a double dispatch system, which is not part of the language.

Upvotes: 3

Related Questions