Sign in to follow this  
v0dKA

Avoiding Downcasting

Recommended Posts

I recently started a text RPG. I have something of a framework done, but it is not working. I find myself needing to downcast, or treat a base object as if it was one of its derived classes. I have an NPC class and I have a Monster class. Monster is a type of NPC, thus it derives from NPC. I also have a global list of all NPCs and their sub-types in a single vector. Here's the problem: the class Monster has a member function Fight(); NPC does not. NPC does, however, have a isMonster() function. Here's what I kind of want to do:
NPC* pNPC;
...
if( pNPC->isMonster() )
     pNPC->Fight();
As expected, the compiler complains. As I am still an OOP newbie, I would like to redesign my class structure in the most commonly accepted, OOP-standard way without introducing any black magic or sphaghetti code. What should I do?

Share this post


Link to post
Share on other sites
You have a few options. The first option is to not lose the type information in the first place: create a separate list of monsters that you then loop over when you want them to fight. A second choice is to use dynamic_cast to cast your NPC pointer to a monster pointer. If the pointer is non-null, then you can then call the fight function on the pointer. A third choice is instead to make the call to Fight() as part of a virtual function that the NPC class provides. Ex:


class NPC {
public:
virtual void Update() = 0;
};

class Monster : public NPC {
public:
virtual void Update() {
Fight();
}
private:
void Fight();
};

Each of your derived classes will implement update appropriately and you would call Update() instead of Fight() when you loop over the NPCs.

Share this post


Link to post
Share on other sites
Since fighting is such a general concept, I don't see why NPC couldn't have a virtual fight function. If you wanted to specifically disallow calling this on other types, you could always make the default throw an exception at runtime. There isn't really a way to catch this at compile time without downcasting that I can think of. But surely any NPC would be able to "fight" in some way?

The only time to be careful of this approach is when you start introducing virtual functions at the top of the hierachy that only apply to specific types. If, for example, monsters (and only monsters) could eat rocks, you wouldn't really want an eat_rocks function in NPC that did nothing or threw an exception by default, but I would think fighting would apply to any NPC.

Sorry if I am missing something.

Paul

Share this post


Link to post
Share on other sites
Sometimes a bit of renaming makes the "provide the function in the base class anyway" approach more palatable.


// Obviously not everything is shown for every class
class NPC {
public:
virtual void provoke() = 0;
};

class Monster: public NPC {
void provoke() {
doFightingStuffHere();
}
};

class Dog: public Monster {};

class Shopkeeper : public NPC {
bool provoked;
Dog pet;
public:
void provoke() {
if (provoked) {
if (pet.dead()) { cry(); } else { pet.provoke(); }
} else {
cout << "Watch it sir, or I'll sic the dog on you!" << endl;
provoked = true;
}
}
};

Share this post


Link to post
Share on other sites
Quote:
Original post by Ocelot
NPC* pNPC;
...
if( pNPC->isMonster() )
static_cast<Monster*>(pNPC)->Fight();


Why did you use static_cast instead of dynamic_cast? To the OP, everything else in this example is the code for SiCrane's second option. Use dynamic_cast instead of static_cast though.

Share this post


Link to post
Share on other sites
Quote:
Original post by Xai
Quote:
Original post by Ocelot
NPC* pNPC;
...
if( pNPC->isMonster() )
static_cast<Monster*>(pNPC)->Fight();


Why did you use static_cast instead of dynamic_cast? To the OP, everything else in this example is the code for SiCrane's second option. Use dynamic_cast instead of static_cast though.



To my understanding, it is safe to use static cast if the type of the object is known. However, for my code, I finally decided to simply separate the NPCs from the Monsters since they are so fundamentally different in the way I intend to handle them. A Monster may be derived from an NPC, but it need not be stored in the same array.

Thanks for all your help!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this