0

I am having several problems trying to downcast a class into another one to access an specific method of that class. This is my current scheme of classes:

GameObject class:

class GameObject
{
...
}

Enemy class:

#include "../GameObject.h"

class Enemy : public GameObject
{
Enemy(Type type);
virtual ~Enemy();
virtual int receiveDamage(int attack_points);
virtual void levelUp() = 0;
...
protected:
char *name_;
int level_;
int health_;
int max_health_;
int attack_;
int armor_;
}

SmallGoblin class:

#include "../Enemy.h"

class SmallGoblin : public Enemy
{
public:
SmallGoblin();
~SmallGoblin();

void levelUp();
}

In my code, I try to do this and a std::bad_cast exception is thrown every time.

class Player : GameObject
{
...
virtual void attack(GameObject &enemy)
{
try
    {
        Enemy &e = dynamic_cast<Enemy&>(enemy);
        e.receiveDamage(attack_points_);
    }
    catch(const std::bad_cast& e)
    {
        std::cerr << e.what() << '\n';
        std::cerr << "This object is not of type Enemy\n";
    }
}
...
}

(enemy is a reference to a GameObject object, however I know it's actually a SmallGoblin object).

In other part my code I have anoother class (Door) which extends the GameObject class and the downcasting works (however, I have to use static_cast instead dynamic_cast I don't know why).

Benjamin Bannier
  • 55,163
  • 11
  • 60
  • 80
Kactung
  • 690
  • 2
  • 8
  • 21
  • Could you provide an SSCCE? – chris Apr 28 '13 at 21:21
  • I'll update the question with my real code. – Kactung Apr 28 '13 at 21:21
  • If you have to use `static_cast`, you're probably getting undefined behaviour. – Joseph Mansfield Apr 28 '13 at 21:23
  • So does `GameObject` have any virtual functions? – john Apr 28 '13 at 21:26
  • @sftrabbit I know, but dynamic_casting isn't working however :/ – Kactung Apr 28 '13 at 21:26
  • You can cast a `GameObject &` to a `SmallGoblin &` directly: `static_cast(enemy)`. – Kerrek SB Apr 28 '13 at 21:32
  • @KerrekSB: Only if the `GameObject` is actually a `SmallGoblin`. Presumably, the game has other enemy types in it, hense the `dynamic_cast`. – Remy Lebeau Apr 28 '13 at 21:33
  • @RemyLebeau: The OP says "however I know it's actually a `SmalLGoblin` object". I'm assuming the OP isn't lying to us. – Kerrek SB Apr 28 '13 at 21:34
  • @KerrekSB: for **this particular example**, yes, but I would assume that in the larger scope of the project, the OP is not going to focus on just `SmallGoblin` objects. – Remy Lebeau Apr 28 '13 at 21:35
  • I just love reading you, guys, talking about `Small Goblins`. Sounds exquisitely nerdy. – lapk Apr 28 '13 at 21:36
  • @Puyover: why not just make `attack()` take an `Enemy&` as input to begin with? Then you don't have to worry about casting. Or do you plan on allowing players to attack `Door`s as well, for instance? If so, then `receiveDamage()` should be moved to `GameObject()` itself, or else `attack()` should be overloaded for different types of input. – Remy Lebeau Apr 28 '13 at 21:36
  • @RemyLebeau The flow the code is working, is that the player checks collisions with every GameObject around him, and then a switch statement is executed to determine which type of GameObject we have. So I will have to downcast after all... – Kactung Apr 28 '13 at 21:40
  • @Puyover: If you have to manually check the object's type and then cast it, then you are not utilizing polymorphism correctly. If the player collides with an object, let the collided object decide what to do with the collision (damage, no-op, etc). Make the decision function virtual in `GameObject` so objects can override it. – Remy Lebeau Apr 28 '13 at 21:44

3 Answers3

4

You mention in one of your comments that you are storing std::vector<GameObject>. Unfortunately, this will cause your GameObject objects to be sliced. A great description can be found here: What is object slicing?

"Slicing" is where you assign an object of a derived class to an instance of a base class, thereby losing part of the information - some of it is "sliced" away.

To solve this problem, you need to store a vector of pointers. You have some choices here, if you're using C++11. You can store either:

std::vector<GameObject*> badGameObjects;
std::vector<std::unique_ptr<GameObject>> uGameObjects;
std::vector<std::shared_ptr<GameObject>> sGameObjects;

All of these options will ensure that slicing doesn't happen because the vector is just storing pointers. Storing naked pointers is the least desirable option because you will have to manage the memory yourself and can be a source of memory leaks. The use of unique_ptr or shared_ptr will depend on how you need to use the objects.

Community
  • 1
  • 1
Steve
  • 7,171
  • 2
  • 30
  • 52
2

If dynamic_cast is failing, then enemy is not actually a valid Enemy instance, so double-check how you are managing that reference.

The following works fine for me when I try it:

class GameObject
{
public:
    virtual ~GameObject(){}
};

enum Type {goblin};

class Enemy : public GameObject
{
public:
    Enemy(Type type) : type_(type) {}
    virtual ~Enemy() {}
    virtual int receiveDamage(int attack_points) {}
    virtual void levelUp() = 0;
protected:
    Type type_;
    //...
};

class SmallGoblin : public Enemy
{
public:
    SmallGoblin() : Enemy(goblin) {}
    ~SmallGoblin() {}

    void levelUp() {}
};

class Player : GameObject
{
public:
    int attack_points_;

    virtual void attack(GameObject &enemy)
    {
        try
        {
            Enemy &e = dynamic_cast<Enemy&>(enemy);
            e.receiveDamage(attack_points_);
        }
        catch(const std::bad_cast& e)
        {
            std::cerr << e.what() << '\n';
            std::cerr << "This object is not of type Enemy\n";
        }
    }
};

.

Player p;
SmallGoblin goblin;
p.attack(goblin);

BTW, I would use dynamic_cast with pointers instead, to avoid the unnecessary overhead of using exceptions:

virtual void attack(GameObject &enemy)
{
    Enemy *e = dynamic_cast<Enemy*>(&enemy);
    if (e)
        e->receiveDamage(attack_points_);
    else
        std::cerr << "This object is not of type Enemy\n";
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Does it matter if every time I get a GameObject is through a reference to it? Because all my GameObject are stored in an std::vector and in the question's case the reference is passed to 3 function through GameObject&. I don't know if maybe the type may be "lost" in the process... – Kactung Apr 28 '13 at 22:04
  • 1
    @Puyover I suspect that slicing is the source of your problems. If you're using a std::vector then you are suffering from slicing: http://stackoverflow.com/questions/274626/what-is-the-slicing-problem-in-c – Steve Apr 28 '13 at 22:11
  • 1
    You will need to store pointers to `GameObject`s. Better still store smart pointers to `GameObject` such as `unique_ptr` or `shared_ptr` depending on your requirements. – Steve Apr 28 '13 at 22:41
-1

dynamic_cast shouldn't be used for downcasting, I'm pretty sure static_cast does the job. dynamic_cast is used for "upcasting" instead.

lucas92
  • 464
  • 2
  • 11