Jump to content
  • Advertisement
Sign in to follow this  
realh

Is C++ RTTI good or bad?

This topic is 1333 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been aware for a while that many programmers feel that exceptions should be avoided as much as possible in C++, but today I read something that suggested RTTI has its downsides too. Most tutorials say that you should always prefer RTTI casting syntax to C-style, but can it actually cause bugs etc? Or is it just a minor performance issue?

Share this post


Link to post
Share on other sites
Advertisement

RTTI can have performance costs within your code, but depending on where and when you use it within your game, it might not be noticable and might be the "right tool" for the job depending on what you want to do.

 

There are many ways of identifying the type of a variable at runtime (e.g. having a "GetType()" virtual function that returns an enum value, etc) without having to resort to RTTI but sometimes they can just seem like kludges.

 

How and where do you plan to use RTTI, or were you just curious?

Share this post


Link to post
Share on other sites

I was referring more to using the *_cast<> API than explicitly checking types myself. I don't think I need to worry about performance, because the sort of games I'm writing aren't CPU-hungry. I chose C++ (over Java mainly) because of the portability and simple deployment etc rather than for performance.

 

The sort of situation where I would use dynamic_cast is where I'm using abstract classes for portability. For example I might have an abstract class called Image and a GlContext class with a pure virtual method uploadTexture(Image *), and subclasses with implementations for SDL2 and Android (not using SDL2 for Android is another story). In the Android build all Image objects would be AndroidImage and GlContext would always be AndroidGlContext. The implementation of uploadTexture needs access to members of AndroidImage which aren't available in the abstract base, so it has to cast. Seeing as each build can only have one type of subclass perhaps I'd be better off doing away with the asbtract base and have two (or more) different versions of Image and of GlContext. But I thought using OOP this way made sense.

 

There's another situation where I've recently used dynamic_cast a lot, which is harder to explain, it's for automatic creation of a dungeon. To enable the algorithm which makes sure all rooms are reachable (ie have at least one door to another room which has at least one other door to another and so on) each room has two vectors of its neighbours (rooms it shares walls with), those with mutual doors and those without. But there are also groups of rooms I call regions, and there should only be one door (a special door for which you have to find a key) between any two connected regions. This makes the connection algorithm pretty much identical for both rooms and regions so I've implemented it in a base class Connectable, with derived classes Room and Region. The vectors of neighbours are of type std::vector<Connectable *>. However, both Room and Region also need to do other Room-specific and Region-specific things with their neighbours, and it's better to reuse the vectors available in the base class and cast the members from Connectable to Room or Region as appropriate than to maintain separate vectors of the subclass. I'm sure there are other ways I could do this which avoid dynamic_cast, but I think they might lead to unpleasant things like cluttering up the classes with trivial virtual functions or using complicated templates.

Edited by realh

Share this post


Link to post
Share on other sites
The main problem most people have with C++'s RTTI is that it is very complicated to account for all the weird things you can do in the C++ type system. As most people do not need the additional complexity, they will frequently opt to making a much simpler RTTI system to save space and speed things up.

As others have pointed out - relying on type information at runtime is generally not preferred - though sometimes you do need to do it.

The sort of situation where I would use dynamic_cast is where I'm using abstract classes for portability. For example I might have an abstract class called Image and a GlContext class with a pure virtual method uploadImage(Image *), and subclasses with implementations for SDL2 and Android (not using SDL2 for Android is another story). In the Android build all Image objects would be DroidImage and GlContext would always be AndroidGlContext. The implementation of uploadImage needs access to members of AndroidImage which aren't available in the abstract base, so it has to cast. Seeing as each build can only have one type of subclass perhaps I'd be better off doing away with the asbtract base and have two (or more) different versions of Image and of GlContext. But I thought using OOP this way made sense.


In this case, you're assured that what you are getting from your caller is the right type, so static_cast instead of dynamic_cast. If you're really paranoid, you can make your own safe_cast that compares the result of a GetType() virtual call against a public const static member variable of the class that denotes its type.

(Example written in forum - may not be compilable)
class Interface
{
public:
  virtual int GetType() const = 0;
};

class Derived: public Interface
{
public:
  // number doesn't matter, as long as it is unique - some code libraries will use a giant enum
  static const int MyType = 1;

  virtual int GetType() const overrde {return MyType;}
};

template<typename T>
T* safe_cast_interface(Interface* aPointer)
{
  if (aPointer->GetType() == T::MyType)
    return static_cast<T*>(aPointer);
  return nullptr;
}

There's another situation where I've recently used dynamic_cast a lot, which is harder to explain, it's for automatic creation of a dungeon. To enable the algorithm which makes sure all rooms are reachable (ie have at least one door to another room which has at least one other door to another and so on) each room has two vectors of its neighbours (rooms it shares walls with), those with mutual doors and those without. But there are also groups of rooms I call regions, and there should only be one door (a special door for which you have to find a key) between any two connected regions. This makes the connection algorithm pretty much identical for both rooms and regions so I've implemented it in a base class Connectable, with derived classes Room and Region. The vectors of neighbours are of type std::vector<Connectable *>. However, both Room and Region also need to do other Room-specific and Region-specific things with their neighbours, and it's better to reuse the vectors available in the base class and cast the members from Connectable to Rom or Region as appropriate than to maintain separate vectors of the subclass. I'm sure there are other ways I could do this which avoid dynamic_cast, but I think they might lead to unpleasant things like cluttering up the classes with trivial virtual functions or using complicated templates.


Not sure I follow exactly what you're going for here - but the above mentioned "safe_cast" that you make yourself using virtual calls and static member variables should be able to do this too.

Share this post


Link to post
Share on other sites

Most compilers have implemented dynamic cast in a way that moving to the root class is fast, but moving to any other layer in the hierarchy is slow. 

 

If you are testing equality or testing against the base, or if you are jumping to the root all is well.  That's good because that's the most common case in well-written code.

 

If you are jumping to leaf nodes or jumping to intermediate levels in the hierarchy, probably your design is wrong. You are supposed to specialize functionality within a subclass by overriding behavior, not write code that must identify the subclass and then specialize externally. That's the opposite of the correct approach.  

 

As an example, if you are writing code that effectively tests if(dynamic_cast<foo>) { foo variant} else if (dynamic_cast<bar>) {bar variant} else if(dyanmic_cast<baz>) {baz variant}, that's a bad implementation.  Instead just run a function that works on all objects, since that's how inheritance is designed.

 

There is a cost in additional executable size for RTTI information, but it is small and not an issue on the PC.

Share this post


Link to post
Share on other sites


In this case, you're assured that what you are getting from your caller is the right type, so static_cast instead of dynamic_cast. If you're really paranoid, you can make your own safe_cast that compares the result of a GetType() virtual call against a public const static member variable of the class that denotes its type.

I see. I misunderstood the difference between dynamic_cast and static_cast and didn't realise static could go in either direction in the hierarchy. You're right, static_cast would definitely be more appropriate for my first scenario. I would probably stick with dynamic_cast for the dungeon thing though, at least until I'm sure it's debugged. I don't think the problems with dynamic_cast are significant enough in my project(s) to justify writing my own replacement, but writing a wrapper might be a good way to easily switch between dynamic_cast and static_cast for debug and release builds.

Share this post


Link to post
Share on other sites

In this case, you're assured that what you are getting from your caller is the right type, so static_cast instead of dynamic_cast. If you're really paranoid, you can make your own safe_cast that compares the result of a GetType() virtual call against a public const static member variable of the class that denotes its type.

I see. I misunderstood the difference between dynamic_cast and static_cast and didn't realise static could go in either direction in the hierarchy. You're right, static_cast would definitely be more appropriate for my first scenario. I would probably stick with dynamic_cast for the dungeon thing though, at least until I'm sure it's debugged. I don't think the problems with dynamic_cast are significant enough in my project(s) to justify writing my own replacement, but writing a wrapper might be a good way to easily switch between dynamic_cast and static_cast for debug and release builds.


Another safety net trick you can use is to use dynamic_cast in debug, and static_cast in release. That way you can catch bugs in debug, and you get speed in release (with the risk of trashing memory if you do a bad cast - but that's what your debug is for).
 
#ifdef CHECK_CASTS // define this when you want to ensure your casts are correct

template<typename T, typename U>
T* checked_cast(U* aPointer)
{
  T* retVal = nullptr;
  if (aPointer != nullptr)
  {
    retVal = dynamic_cast<T*>(aPointer);
    ASSERT(retVal != nullptr);
  }
  return retVal;
}

#else

template<typename T, typename U>
T* checked_cast(U* aPointer)
{
  return static_cast<T*>(aPointer);
}

#endif
Edited by SmkViper

Share this post


Link to post
Share on other sites


The sort of situation where I would use dynamic_cast is where I'm using abstract classes for portability. For example I might have an abstract class called Image and a GlContext class with a pure virtual method uploadImage(Image *), and subclasses with implementations for SDL2 and Android (not using SDL2 for Android is another story). In the Android build all Image objects would be DroidImage and GlContext would always be AndroidGlContext. The implementation of uploadImage needs access to members of AndroidImage which aren't available in the abstract base, so it has to cast.
You don't need casting, because you don't actually need access to AndroidImage internals: you need a more complete separation between the generic and Android-specific parts of your code. Only Android-specific classes should be allowed to have variables with Android-specific types, while generic code like an uploadImage() function should be restricted to using Image, GIContext, etc. You probably need to add something to the abstract interfaces like Image.


I've implemented it in a base class Connectable, with derived classes Room and Region. The vectors of neighbours are of type std::vector. However, both Room and Region also need to do other Room-specific and Region-specific things with their neighbours, and it's better to reuse the vectors available in the base class and cast the members from Connectable to Rom or Region as appropriate than to maintain separate vectors of the subclass.

This is just a bad design; a Connectable interface doesn't make sense, because you are always connecting regions, which are aggregates of rooms, and some regions are allowed more than one outgoing door.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!