Skip to main content
GameDev.net gamedev.net
🔒 Locked

Casting Pointer to Derived Type

Started by Lith Nov 9, 2014 at 10:31 AM 23 replies 4.4k views
Original Post
Lith
Lith

Eh, this may be kinda stupid, but I have the traditional setup:


class tile {
    // ...
};

class tilePlayer : public tile {
    // ...
};

At one point in my code I had a pointer to a tile instance and I wanted to access it as a tilePlayer instance. So I tried something like this:


// map is an array of pointers to tile instances
(tilePlayer*)map[ index ]->PlayerSpecificFunction();

This didn't compile ( "PlayerSpecifcFunction not member of tile" ) so I tried this and it worked:


tilePlayer* p = (tilePlayer*)map[ index ];
p->PlayerSpecificFunction();

Isn't the latter just the same as the former but with more steps? Or am I messing up the syntax?

Brother Bob
Brother Bob

Your cast binds to the wrong expression:


(tilePlayer*)map[ index ]->PlayerSpecificFunction();

is equivalent to


(tilePlayer*)(map[ index ]->PlayerSpecificFunction());

whereas you want


((tilePlayer*)map[ index ])->PlayerSpecificFunction();

That's another reason to not use C-style casts and use the more explicit C++ style casts, because you cannot get the cast to bind to the wrong expression by accident.


static_cast<tilePlayer*>(map[ index ])->PlayerSpecificFunction();
Lith
Lith

Gah, that was stupid. Thanks.

That's another reason to not use C-style casts and use the more explicit C++ style casts, because you cannot get the cast to bind to the wrong expression by accident.

Thanks for the tip! I'll upgrade my casts.

rip-off
rip-off

Some unrelated points:

* Casting is usually a sign of a mismatch between your code design and your inheritance hierarchy

* Are you sure a player "is-a" tile?

One alternative implementation is that a tile has a pointer to any player that happen's to occupy it, or nullptr if there is no such player. Alternative implementations might decouple the player from the tile by just storing the tile index that the player is "on" (assuming your game has a tile based map that the player moves across).

SmkViper
SmkViper
Also - if you absolutely have to down-cast (and you usually don't, C++ provides a lot of mechanisms to avoid it), then you should use dynamic_cast, not static_cast. dynamic_cast will use RTTI to ensure that the pointer actually is what you think it is and return nullptr otherwise. This way you'll crash immediately trying to access a nullptr value rather then reading/writing random memory.
alvaro
alvaro

If some part of your code has a pointer to a tile and it needs to know the specific type of tile it is, I think there is something wrong with your design.

As rip-off said, are you sure the player is a tile? A tile simulator -where you can experience the excitement of being glued to the floor- seems like an odd premise for a game. :P

Kryzon
Kryzon

Also - if you absolutely have to down-cast (and you usually don't, C++ provides a lot of mechanisms to avoid it) [...]

Could someone please elaborate on this?
Eric Lengyel
Eric Lengyel

The need to cast to a subclass type arises all the time in well-designed architectures. Please stop it with the scaremongering about downcasting being a symptom of bad design. It's not. If you want to eliminate it everywhere, then your code is going to turn to shit because you'll be jumping through all kinds of unnecessary hoops like adding lots of virtual functions to do simple things.

As a basic example, consider a GUI system in which there is a class hierarchy of widgets. Suppose there is a resource format of some kind that stores all the widgets for a dialog box, and suppose that when it's loaded, the widgets of the appropriate subclasses (TextWidget, ButtonWidget, MenuWidget, etc.) are created and stored in a tree representing the layout of the dialog. Now some code is going to load that resource and want to access particular widgets as their specific subclass types, e.g., to change the content of a TextWidget to the user's name or enable/disable a ButtonWidget depending on some condition. However, the function that locates those particular widgets (perhaps by name or some kind of ID number) after the dialog is loaded will always return a pointer to the Widget base class. Knowing what the widget's actual subclass type must be, your program will then use static_cast to change a Widget * into a pointer to a TextWidget *, ButtonWidget *, etc., so it can call the functions specific to those subclasses. There's nothing wrong with this.

BitMaster
BitMaster
Then why did you feel the need to downvote me? The huge advantage of boost::polymorphic_downcast is that it verifies the type in debug builds (always a good idea to assert things which should be true there) and is a static_cast in non-debug builds.
rip-off
rip-off



There's nothing wrong with this.

I don't think that anyone is arguing that a design that requires casting is always "wrong". However, I would argue that the kind of situation you describe is very different from the one presented in the OP. A TextWidget "is a" Widget. It is not obvious how a Player "is a" Tile.

SmkViper
SmkViper


Also - if you absolutely have to down-cast (and you usually don't, C++ provides a lot of mechanisms to avoid it) [...]

Could someone please elaborate on this?



Casting in general is usually a sign of something subtly wrong with the design. It has its use, but things like interfaces, virtual methods, std::function, templates, and other mechanisms can all be used so that you don't have to cast and the code will always do the 'right thing'.

In general - using inheritance should be limited and you should prefer composition. A player is not a tile, but a player may contain a tile that represents its visual appearance in your world. If your code needs to access the player, they should be passed a player, not passed a tile and then trying to figure out through some arcane method whether it is a player or not.

If I'm giving you a tile to work with, then that is the interface you use. If you need something more specific (like, I dunno, "animated tile", which isn't the best example) then ask for the more specific thing and avoid casting.

The need to cast to a subclass type arises all the time in well-designed architectures. Please stop it with the scaremongering about downcasting being a symptom of bad design. It's not. If you want to eliminate it everywhere, then your code is going to turn to shit because you'll be jumping through all kinds of unnecessary hoops like adding lots of virtual functions to do simple things.

As a basic example, consider a GUI system in which there is a class hierarchy of widgets. Suppose there is a resource format of some kind that stores all the widgets for a dialog box, and suppose that when it's loaded, the widgets of the appropriate subclasses (TextWidget, ButtonWidget, MenuWidget, etc.) are created and stored in a tree representing the layout of the dialog. Now some code is going to load that resource and want to access particular widgets as their specific subclass types, e.g., to change the content of a TextWidget to the user's name or enable/disable a ButtonWidget depending on some condition. However, the function that locates those particular widgets (perhaps by name or some kind of ID number) after the dialog is loaded will always return a pointer to the Widget base class. Knowing what the widget's actual subclass type must be, your program will then use static_cast to change a Widget * into a pointer to a TextWidget *, ButtonWidget *, etc., so it can call the functions specific to those subclasses. There's nothing wrong with this.


Your example doesn't really represent the op's issue, but "dialog holding widgets" design does require casting and in this case you're probably right. However if I was doing that I would instead add helper non-member functions like "TextWidget* GetTextWidget(int ID)" that return exactly what I want, pre-casted. This allows me to put all my checks in a single location to make sure that I can return to the user what they asked for, or return null/throw exception/whatever. Internally I'd still use dynamic_cast instead of static_cast because of type-safety. If speed was a major concern then I'd roll-my-own simple RAII for the widget and static_cast after testing the type (maybe only testing the type in debug and asserting if it is wrong). This way I avoid running into issues where I write to random memory because I static_cast-ed something to the wrong type.
Eric Lengyel
Eric Lengyel

However if I was doing that I would instead add helper non-member functions like "TextWidget* GetTextWidget(int ID)" that return exactly what I want, pre-casted.

That would work fine, but it's ugly and unnecessary. You're just wrapping up a perfectly good language feature in an extra set of functions that increase the cost of maintaining the code. (When someone adds a new widget type, will they remember to add the separate Get function, too?)

Internally I'd still use dynamic_cast instead of static_cast because of type-safety.

Experienced game developers do not build with RTTI or exception handling enabled. There is no dynamic casting, and there is no throwing. In this example, each widget should know its own dynamic type (stored in the base class). You could check that type in a debug build and simply assert if you got one different from what you're casting to.

Eric Lengyel
Eric Lengyel

I don't think that anyone is arguing that a design that requires casting is always "wrong".

Maybe not here, but there are definitely people who think that way. In the OP's case, there's nothing to indicate either way whether a tilePlayer is a tile or has a tile, so you have no basis for which you can say my example is very different. You're assuming that a tilePlayer represents the player himself when it could just include some extra rendering data for tiles that show player locations. Maybe a player object somewhere else has a reference to a tilePlayer, which is a special type of tile, and all tiles are stored in some list of pointers to the tile base class. We don't know.

Eric Lengyel
Eric Lengyel

Then why did you feel the need to downvote me? The huge advantage of boost::polymorphic_downcast is that it verifies the type in debug builds (always a good idea to assert things which should be true there) and is a static_cast in non-debug builds.

Again, the pros do not use RTTI and exception handling, even if it's only in debug builds. If you want to ignore the collective wisdom of all the top developers in the industry, you do so at your own peril.

frob
frob

This is also an area where virtual functions, function tables, and delegates/events are all excellent alternatives.

Again, the pros do not use RTTI and exception handling, even if it's only in debug builds. If you want to ignore the collective wisdom of all the top developers in the industry, you do so at your own peril.


Be very careful with that. It is not advice to be blindly followed.

Practices change over time as hardware and software evolves. There was a day when it was more cost effective to do in-place swaps. That is: a xor b, b xor a, a xor b. However, those days ended in the early 1990s with pipelined processors. Yet even now, two decades after it became terrible advice that actually stalls the processor, I sometimes see people make it as a performance suggestion.

RTTI and C++ exceptions have very specific costs that get incurred. Those specific costs are getting less and less relevant.

  • The data for both RTTI and C++ exceptions requires more space. The current generation of game consoles have enormous systems. Consider the Nintendo DS that was quite popular until its successor the 3DS arrived 3 years ago: 66MHz with 4MB RAM and cartridges started at 8 megabytes. Or consider the PS2 that was only discontinued 2 years ago: 300MHz and 32MB RAM where a good chunk was taken by the system. The space and overhead required for RTTI and C++ exceptions could be an enormous concern, but is less of a concern today, especially on a modern PC.
  • With dynamic casting and other RTTI operations, it is potentially inexpensive to do a same-type comparison, and potentially inexpensive to work on the endpoints of the tree depending on most implementations. Typically these can be implemented with a few integral comparisons. Comparions of types to the root of the tree is a fast operation, but comparing any other position being a potentially expensive operation potentially reaching hundreds of nanoseconds. Dynamic casts are potentially expensive, on slower hardware individual casts can potentially require microseconds. Considering console games often have about 16,000 microseconds per frame, a single completely avoidable cast that requires about one ten thousandth of your time is a bad idea. Putting it in a loop where an avoidable operation costs one percent of your compute time is lunacy. The operations are relatively less expensive today on more advanced hardware, approaching the point where certain uses are quite acceptable.
  • There is additional overhead in function calls to provide frame information for exceptions. Today's compilers are able to reduce it but not completely eliminate it. On modern systems the overhead individually is small, measured in nanoseconds, but multiplied by all the locations it can consume a large number of microseconds every second. If you are working on games where various developers are hunting for performance, disabling exception handling on some systems could give enormous per-frame reclamations, sometimes the overhead of exceptions consumed 10% of the available CPU time. On modern systems the performance difference is much smaller. Disabling C++ exceptions comes with a cost of course -- certain aspects of the language become unavailable -- but the performance benefits historically have far outweighed the extra work to support it. On more modern systems the difference is sometimes negligible.

Times are changing. Hardware is changing and "too slow" becomes "fast enough". Every game is different, for many those factors are not even a concern.

If those are not concerns for your game then you should probably not follow the advice.

Servant of the Lord
Servant of the Lord

Again, the pros do not use RTTI and exception handling, even if it's only in debug builds. If you want to ignore the collective wisdom of all the top developers in the industry, you do so at your own peril.

"Real programmers only program in Haskell while hanging upside-down over a volcano!"

Some informed decisions skilled programmers make in specific circumstances (or even previously common-place circumstances) aren't universally applicable to every circumstance.

"Faceless crowd of people X don't use feature-Y, so if you use feature-Y you'll never be able to join the X in-group where all the cool people are."

BitMaster
BitMaster

Again, the pros do not use RTTI and exception handling, even if it's only in debug builds. If you want to ignore the collective wisdom of all the top developers in the industry, you do so at your own peril.

Just for the record, the primary reason I downvoted this is your abrasive personality in this thread and some other places I noticed you recently. An attitude like "I'm awesome, so every turd I drop is pure gold and you do not need anything else" really bugs me.

Now, moving on, especially after reading frob's post the impression I get this is solely about console considerations. To be honest, I'm getting rather tired of stuff being only valid for consoles ruining people's day on non-console systems and I'm talking both about the actual programming as well as unnecessarily limiting gameplay decisions.

Yes, there is a lot of money to be made on consoles. But consoles also cover only a very limited selection of genres and as it happens, aroundish 99% of what gets published on consoles is of no to little interest to me. What does interest me hardly ever makes it to consoles, because the average console audience is not very interested in it, because the people interested in the genre are less likely to have a console and because playing these kinds of games with a controller is not very optimal.
I don't want to go into ideological battles here, so let's just say there is a market outside of consoles sufficiently large to make a decent living from. That's one part of the argument.

The other part is, unless you are part of a big studio, you will never write on consoles (at least not in C++ where a decision of "RTTI" or "no RTTI" could actually be made). Yes, consoles are very different beasts with specific requirements. But should I ever work on one I will have to read a lot of documentation and best-practice guidelines about it anyway (much of it is usually hidden by NDAs before I actually work on one), not to mention the studio's own coding guidelines.
I would expect from any decent programmer to be able to modify their coding behavior depending on constraints like this. In my mind, if "we don't have RTTI on this platform" is too much for people to handle, they are really not people who should be working on the project.
This forum is called "General Programming". It's not called "Console Programming", there are specialized places for that (usually inaccessible to the public because of the aforementioned NDAs and stuff). I would have no problem with people contributing additional information from their own experience how something is extremely inadvisable from their own console experience. But what I read very frequently is people taking their console experience and steamrolling it over normal (game) development.
There is a a whole ecosystem of non-console game development out there. Your profits are not measured in hundreds of million of dollars, but neither are your costs. Also, you can actually work on the platforms involved without jumping through very expensive and often unreachable hoops.
Completely ignoring this reeks to me as ignorance at best and actual malignancy at worst.
DiegoSLTS
DiegoSLTS
You're assuming that a tilePlayer represents the player himself when it could just include some extra rendering data for tiles that show player locations. Maybe a player object somewhere else has a reference to a tilePlayer, which is a special type of tile, and all tiles are stored in some list of pointers to the tile base class. We don't know.

At first I thought the same while reading the "probably a bad design" posts, a "tilePlayer" could be a tile with some aditional information for a player tile and the Player could be something else. But then in the example code that the OP wrote it says:


tilePlayer* p = (tilePlayer*)map[ index ];
p->PlayerSpecificFunction();

which looks like the tilePlayer is being used as the player, with a specific player function in it.

So, with the little information given, I understand why for most people there's an alarm to re-check the design.

frob
frob

Now, moving on, especially after reading frob's post the impression I get this is solely about console considerations. To be honest, I'm getting rather tired of stuff being only valid for consoles ruining people's day on non-console systems and I'm talking both about the actual programming as well as unnecessarily limiting gameplay decisions.


Not at all. The concerns used to be for desktop PCs as well. Sometimes they still are.

The very strong advice to turn off both RTTI and C++ Exceptions comes from the mid 1990s when the features were introduced. That is when the desktop PC was around 150MHz-200Mhz and a full gigabyte drive was becoming affordable.

The key thing about performance advice is that too many people leave off the reason behind the change. The reasons behind the guidance will eventually change, making the advice irrelevant or even harmful.

A blind assertion "Professional use this option, so you should too!" is shortsighted and often unhelpful. A more educated and educational guidance "many people use this option, it is useful under these scenarios, it causes these changes" is far more valuable. The more detailed version lets you see not just what the change is doing, but also guides you to know what to measure to see if it is useful in your case, and also helps you determine when it will not have any significant effect.


In this case the advice WAS relevant two decades ago, was mostly relevant a decade ago to everybody, and is even occasionally relevant for mainstream game developers presently. The guidance may help you even today if you are right on the cusp of a performance boundary, or if your project needs the performance.

Both RTTI and C++ Exceptions have very real costs to the executable. They use the same mechanism, so most people use them as a pair, both or none. You pay those costs if the compiler options is enabled, including paying a cost even if you don't directly use the feature. Most high performance computing systems turn the features off because they do not want to incur the costs. It is a tradeoff, sacrificing specific language features and benefits in exchange for specific performance improvements.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.