Casting Pointer to Derived Type

Started by
23 comments, last by SmkViper 9 years, 5 months ago
To add on to the conversation there are always cases where people shout "Don't use X, it's horrible for performance!" where X is anything from exceptions, to RTTI, to templates, to virtual calls, to C++, to C. After all, "real" programmers use assembly!

Now days you'd be hard pressed to find a AAA game that didn't take advantage of C++ and most of its features like virtual dispatch and templates that would have been anathema only 5-10 years ago. But compilers have gotten better at optimizing, and computers have gotten faster (to a point - they're kind of plateaued at the moment).

Modern compilers can make exceptions have zero run-time cost if they're not thrown (actually zero, not "few microseconds zero"). But they have an unknown/variable cost when thrown, which makes them unacceptable for certain kinds of things, they increase code size, and rely on RTTI - which will also increase data size.

And that's not even getting into the fact that most people's code - especially old code - is incredibly exception unsafe where they would crash, leak, or worse should an exception propagate through. It's kind of hard to convince management to make your code support exceptions when it won't (directly) add a bullet-point on the back of a box.

However even if they aren't using exceptions/RTTI I think people can - and should - make their code "exception safe", as doing so generally makes your code more robust in general for minimal cost. Doing things like using smart pointers, proper RAII for resources and any other cleanup code, and the like just makes it harder for your code to break - and being safe in the face of exceptions is a bonus you can capitalize on later.

Just remember: Guidelines are guidelines. Not absolutes. Break them - but be sure you can explain to someone why you're breaking them - and preferably back it up with hard data (like profiling logs).
Advertisement

SmkViper, I agree with most of your post, but I'd like to point out that exceptions do not actually have a zero run-time cost like compiler vendors would like you to believe. In my tests, it has about a 1% performance impact to simply enable EH and RTTI in the compiler without a single usage in the code (no try, catch, throw, typeof, or dynamic_cast anywhere). This is very small, yes, but it's measurable, and I would not argue against someone claiming that it's worth it to get the extra functionality of EH if that's what somebody really wanted. The overhead comes from the fact that a larger amount of code and data is generated by the compiler, and this has an affect on cache usage while the program is executing. Fragments of code that used to be side by side in the same cache line are now separated by a little extra code that the compiler inserted to handle an exception being thrown. And the zero overhead claim only applies to throw statements. Once you start sprinkling throw...catch blocks in the code, it does have a nonzero cost in terms of actual instructions executed, including new branches that wouldn't exist otherwise.

But as you discussed, the real downside to enabling EH is the fact that the programmers have to worry about what will happen for every function they call if an exception is thrown inside that function, and they have to jump through extra hoops to make sure everything is cleaned up properly in the case that the called function doesn't return and the remaining code in the calling function doesn't get executed. I'm normally a big proponent of writing clean, bulletproof code, but the burden of wrapping up all your pointers and implementing strong RAII for absolutely everything goes a little too far for me. There most definitely is the matter of programmer productivity to consider here, and it is more expensive in terms of time and money to write exception-safe code.

BitMaster, the more I think about it, the more I'm OK with using something like polymorphic_downcast with RTTI enabled in debug mode, so I think I may have overreacted to it. It's just that using RTTI or Boost are both things that would make virtually all of my peers give me a dirty look, and seeing them suggested together at once made me go all hulk-smash. Sorry if I came across as abrasive, but nothing I said justifies you resorting to name calling and derision of my qualifications. (And if you're going to do more, please grow a pair and use your real name.)

But as you discussed, the real downside to enabling EH is the fact that the programmers have to worry about what will happen for every function they call if an exception is thrown inside that function, and they have to jump through extra hoops to make sure everything is cleaned up properly in the case that the called function doesn't return and the remaining code in the calling function doesn't get executed. I'm normally a big proponent of writing clean, bulletproof code, but the burden of wrapping up all your pointers and implementing strong RAII for absolutely everything goes a little too far for me. There most definitely is the matter of programmer productivity to consider here, and it is more expensive in terms of time and money to write exception-safe code.

I'm not sure that's an excuse. With C++11 you have std::unique_ptr and that's a simple RAII container (by default not any more expensive than doing it by hand) for any pointer you new. Admittedly, it's not as simple if you need a specialized destroy()-call. You either need a (possibly stateful) functor object but you might be able to template-specialize (of std::default_delete) the problem away for most scenarios.
Even without the danger of a thrown exception I see automatic, inexpensive cleanup as a huge advantage.

BitMaster, the more I think about it, the more I'm OK with using something like polymorphic_downcast with RTTI enabled in debug mode, so I think I may have overreacted to it. It's just that using RTTI or Boost are both things that would make virtually all of my peers give me a dirty look, and seeing them suggested together at once made me go all hulk-smash. Sorry if I came across as abrasive, but nothing I said justifies you resorting to name calling and derision of my qualifications. (And if you're going to do more, please grow a pair and use your real name.)

I explicitly said "something like boost::polymorphic_downcast" (emphasis added here). It's quite literally a three liner you can write into any local utility header without any further reference while you wait for the coffee maker.
That said, I never understood the aggressive dislike some people have of Boost. Granted, some corners of the library can be a bit iffy, but especially the (rather large) header-only part of the library contains several bits and pieces I wouldn't want to live without. Either those parts of Boost should be available or an alternative, hand-rolled implementation should be available in the project's utilities.

Performance et. al. are not arguments in this matter.

dynamic_cast is thee mechanism designed into C++ to handle the exact scenario the OP is asking about.

If you don't want to use RTTI then you can't use RTTI and down-casting properly requires RTTI.

If you turn it off, you are just going to have to implement it yourself with a virtual call to get an enumeration (and design some mechanism to ensure those ID's are unique).

Next is if you use a interface-based design then your RTTI implementation is going to have to handle reporting multiple IDs ...

COM's QueryInterfacePointer is the abomination that resulted from going down this path.

If you have a tree or whatever of ITiles and want to know which of them implement ITilePlayer then you check for that when you put the instance into the container (with dynamic_cast) and maintain a second parallel container with just ITilePlayer's in it. Now you can iterate the player tiles without testing for it during an iteration of the general tiles thus avoiding dynamic cast during the part that matters. (Since it's a mix of different types of tiles, static_cast is not appropriate.)

I personally wouldn't consider the player a tile - I would have an 'overlay' concept the movable entities / sprites implemented.

Render in two (or more) passes. Render the tiles then render the entities (overlayed on the tiles).

You now have a game-engine reason to main separate list of the different things as well.

Though it does beg the question why not just have two functions to add stuff to the game and treat the tiles and entities differently (thus avoiding RTTI & dynamic cast entirely).

- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara

COM's QueryInterfacePointer is the abomination that resulted from going down this path.


Hey now, be fair. COM has to do everything itself because it's language-agnostic and has to implement a lot of things in a C API that C doesn't have language support for. smile.png

This topic is closed to new replies.

Advertisement