7

I'm building a simple game design for a project of mine. I have the following classes:

class Character
{
public: 
   virtual void Display();
   virtual void SetParameters( char* param, ... );
};

class NonPlayableCharacter : public Character
{

public:
   virtual void Display();
   virtual void SetParameters( char* paaram, ... );
   int GetNPCState();
}

And then I have a bunch of classes that derive either from Character or NonPlayableCharacter. I define it like so:

std::vector<Character*> _allChar;

My problem is that at any given time I would want to perform some operation on one of the element of the vector. So getting an element out of the vector I can't directly call the method GetNPCState() because the element in the vector are of Character* type. So doing this:

_allChar[0]->GetNPCState();

doesn't work. So I tried doing it with the famous dynamic_cast:

NonPlayableCharacter* test = dynamic_cast<NonPlayableCharacter*>(_allChar[0]);
test->GetNPCState();

The problem with this last attempt is that GetNPCState() crashes because the object is null, and I know for a fact (via debugging) that _allChar[0] isn't null.

TurnsCoffeeIntoScripts
  • 3,868
  • 8
  • 41
  • 46
  • 2
    You get a null pointer when the object you are trying to cast to `NonPlayableCharacter` isn't in fact a `NonPlayableCharacter`. Probably some of the objects in the vector are directly derived from `Character`. – jogojapan Oct 22 '12 at 14:21
  • As an aside, I work with a component(s) which uses many vectors of pointers. It has caused a helluva lot of trouble for me, as this vector ends up sometimes with pointers to deleted memory, and it leads to memory corruption. I guess that you use a pointer vector because you are using it polymorphically, and/or the objects are shared. In either case I would HIGHLY recommend changing your vector to hold a shared pointer type (`std::shared_ptr` for example) or change your vector to `boost::ptr_vector` (which is great for both scenarios). Good luck with the game! – Dennis Oct 22 '12 at 14:37

7 Answers7

6

There are several types of casts in C++ (4), of which 2 are of interests here:

  • static_cast assumes that you know what you are doing
  • dynamic_cast checks, at runtime, that you "guessed" right

Note: simplified, as dynamic_cast also allows cross-casts and casts involving virtual bases.

There are 3 versions of dynamic_cast, really, depending on the nature of the target:

  • if the target is a reference (ie dynamic_cast<T&>(u)), then if the check fails dynamic_cast throws a std::bad_cast exception
  • if the target is a pointer (ie dynamic_cast<T*>(p)), then if the checks fails dynamic_cast returns a null pointer
  • finally, as a special case, if the target is void*, then dynamic_cast instead returns the address of the full object

In this case, you may:

  • switch from dynamic_cast<NonPlayableCharacter*>(_allChar[0])->getNPCState() to dynamic_cast<NonPlayableCharacter&>(*_allChar[0]).getNPCState(), and let the exception propagates
  • test the result of the cast (NonPlayableCharacter* test here) for non-nullity
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
5

After

NonPlayableCharacter* test = dynamic_cast<NonPlayableCharacter*>(_allChar[0]);

you should check if test is NULL. Even if _allChar[0] isn't NULL, the dynamic_cast can return NULL if the object it points to isn't a NonPlayableCharacter.

So the correct version would be:

NonPlayableCharacter* test = dynamic_cast<NonPlayableCharacter*>(_allChar[0]);
if (test)
{
   test->GetNPCState();
}
Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
3

dynamic_cast returns NULL if cast is impossible. Check what is inside _allChar[0]. You could make function like getType() where returns object predefined type id and then use static_cast:

if (_allChar[0]->getType() == TYPE_NO_PLAYER) {
    static_cast<NonPlayableCharacter*>(_allChar[0])->getNpcState();
}
Denis Ermolin
  • 5,530
  • 6
  • 27
  • 44
2

You have to test the dynamic_cast for success. It returns a null pointer upon failure:

NonPlayableCharacter* test = dynamic_cast<NonPlayableCharacter*>(_allChar[0]);
if (test) test->GetNCPState();

The problem could be that your first element does not point to a NonPlayableCharacter object.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
2

dynamic_cast returns NULL when its argument doesn't point to a NonPlayableCharacter (so the first element in the array probably points to some other subclass of Character) - so you need to check for NULL after the cast. However, using dynamic_cast might be indicative of a design problem. Perhaps you should instead have a virtual method on Character that is called e.g. PerformMainActionInGameLoop() and is overridden appropriately in the different subclasses?

Aasmund Eldhuset
  • 37,289
  • 4
  • 68
  • 81
2

To get a child pointer from a base pointer you do have to use dynamic_cast. Its behaviour is the following:

  • If the pointer points to a Child* allocated with new Child then dynamic_cast<Child*> returns a Child*
  • If the pointer points to something else, then dynamic_cast returns NULL.

Your problem is that either you didn't allocate with new, or your object is of a different type.

alestanis
  • 21,519
  • 4
  • 48
  • 67
2

There is probably a better OO solution to using dynamic_cast, but the whole point of using this cast is that it will return a NULL pointer if the cast fails.

So check for NULL before you call GetNPCState();

NonPlayableCharacter* test = dynamic_cast<NonPlayableCharacter*>(_allChar[0]);
if( test != NULL )
{   
     test->GetNPCState(); 
}
CashCow
  • 30,981
  • 5
  • 61
  • 92