7

Given a base class of gameObject and a derived class of animatedGameObject, I thought it may be good to store all of their instances in an std::vector. If the vector GameObjects is declared to be the base type of gameObject*, derived object instances require casting.

Example:

vector<gameObject*> GameObjects;

gameObject A* = new gameObject( ...init... ); 
animatedGameObject B* = new animatedGameObject( ...init... );

GameObjects.push_back(A);
GameObjects.push_back(B);

// to access the animatedGameObject functions:
static_cast<animatedGameObject*>(GameObjects[1])->functionUniqueToAnimated();

Being afraid as per usual, I turned to Scott Meyers (Effective C++, 3rd Edition), who writes on the subject:

Many programmers believe that casts do nothing but tell compilers to treat one type as another, but this is mistaken. Type conversions of any kind (either explicit via casts or implicit by compilers) often lead to code that is executed at runtime.

I've read through his Item 27: Minimize Casting twice, yet given my inexperience with this, I am struggling with my inability to answer a simple question "IS THIS A DUMB THING TO DO?"

I should mention that there are several reasons why it is a dumb thing to do that have nothing to do with invoking static_cast. The questions, in order of importance, are:

  1. Am I not seeing some possible risks with the use of static_cast in my example above?
  2. Are there better data structures than the std::vector for such approaches? (only if there's one that is obvious, I'm not asking you to do my research for me.)

This is my first time asking a question here, so apologies in advance, where necessary.

ilzxc
  • 222
  • 1
  • 7
  • +1 for having read Effective C++ – Borgleader Jul 01 '13 at 03:04
  • 1
    The usual thing to do here would be to define `draw()` as a virtual function in the base class. You could then simply call it with no casts and it would do the right thing. Are you aware of this, but want to know about the effects of the cast specifically? – jogojapan Jul 01 '13 at 03:07
  • Yes, I should have used a better function name, and yes to the question at the end of your comment. – ilzxc Jul 01 '13 at 03:15
  • How do you kniw `[1]` contains an animated object? I mean in the real version of your code. Often that information can be moved to program structure, or exploited to package the cast better, or eliminate it. – Yakk - Adam Nevraumont Jul 01 '13 at 03:29

2 Answers2

7

static_cast is not the right tool for the job, unless you know that the pointer goes to an animatedGameObject and not a gameObject. What data structure are you using to store that information?

Determining the type of a derived object after a base pointer is the job of dynamic dispatch or dynamic_cast. In your example, the call GameObjects[1]->draw() should work with no cast because draw should be a virtual function. Otherwise you can use dynamic_cast< animatedGameObject & >( * GameObjects[1] ) to assert that the object is an animatedGameObject, and throw a std::bad_cast exception if it's not. (This would still require a virtual function in class gameObject , usually its destructor.)

But doing static_cast to a polymorphic derived type is a code smell.


Also you ask whether std::vector is a good data structure for this use case. It is, but not a vector of "naked" pointers. C++11 now provides "smart pointer" memory management classes which perform new and delete for you, rendering the actual operators all but obsolete. Look into std::unique_ptr for this case.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 1
    I'll just another vote/more visibility for making `draw` virtual so this can be done without casting. This is one of the *classic* examples of things for which virtual functions work essentially perfectly. – Jerry Coffin Jul 01 '13 at 03:12
  • 1
    @jogojapan D'oh, I confused myself. A custom deleter can make `unique_ptr` handle non-polymorphic types polymorphically. Will fix. – Potatoswatter Jul 01 '13 at 03:19
  • Thank you very much. Going to go read up on `std::shared_ptr` – ilzxc Jul 01 '13 at 03:22
  • @ilya I made a mistake, `std::unique_ptr` will be OK without any advanced techniques. I had been thinking of something else: making non-polymorphic types behave polymorphically. – Potatoswatter Jul 01 '13 at 03:23
1
  1. If gameObjects[1] is not animatedGameObject, application will (most likely) die horribly.
  2. If draw() is virtual method that is present in gameObject, conversion is unnecessary.

In general, casting base class to derived class is unsafe. In those situations it makes sense to use dynamic_cast. Dynamic_cast returns NULL, if conversion cannot be performed.

Are there better data structures than

Well, IF your gameObjects are not automatically deleted elsewhere, it might make sense to use something like std::vector<std::shared_ptr<gameObject> >. However, standard shared pointers may introduce hidden overheads (extra new/delete, in worst case scenario they might even introduce multithreaded mutex lock, IF they're designed to be thread-safe), so you should make sure those overheads are compatible with your goals.

SigTerm
  • 26,089
  • 6
  • 66
  • 115