[GD-General] Multiple Inheritance and RTTI
Brought to you by:
vexxed72
From: Oscar C. <osc...@cr...> - 2003-02-04 10:52:34
|
Hi people, Apologies for the length of this post. I'm interested in hearing about real-world experiences of using run-time type information with multiple inheritance in cross-platform (PC, PS2, Xbox, NGC) C++ games. The situation I'm considering is where all game entities are derived from a common Entity base class, and implement zero, one or more interface base classes. I've got a pointer to an Entity object. I want to know if it supports a particular interface, and get a pointer to that interface if it is supported. I'm guessing that there will be less than a hundred derived classes, no more than thirty two interfaces, and a few hundred object instances at any given time. I'd expect there to be less than a thousand interface queries per frame, but this is the least well known metric. Clearly there are a number of approaches available, but I have no hard facts to suggest which is likely to be most appropriate. Out-of-the-box C++ provides the solution by enabling RTTI language support and using dynamic_cast: IRenderable* const renderable = dynamic_cast<IRenderable*>(entity); if (renderable != 0) renderable->Render(); On the plus side, this is simple and bug-free. However, I've received very mixed messages about the virtues and vices of dynamic_cast. Some people say it bloats code, others say it's lean and mean. Some say it's lightning fast, others that it's appallingly slow. I've never used it heavily enough to notice a hit. The next most simple approach seems to be for the Entity base class to provide a virtual accessor function for each interface, which returns NULL by default but can be overriden by a derived class: IRenderable* const renderable = entity->GetIRenderable(); if (renderable != 0) renderable->Render(); One major drawback here seems to be that the vtable for each Entity class will be bloated by over a hundred bytes, which seems like a bad move for cache limited situations. Finally, the Entity base class could provide a single virtual accessor function that takes an interface identifier as a parameter: IRenderable* const renderable = reinterpret_cast<IRenderable*>( entity->QueryInterface(IID_IRenderable)); if (renderable != 0) renderable->Render(); There are two obvious implementations of this method: The derived class either implements the function as conditional code, or maintains some kind of look-up table. The former seems more favourable as a cross-platform solution, again for cache reasons, but contains the most implementation pitfalls. Any of these methods could be supplemented with an inexpensive test for interface support by adding a constant data overhead to each Entity object (in this case, four bytes). Each interface is assigned an identifier, which corresponds to a single bit in a bitfield owned by the base class: class Entity { public: enum InterfaceID { IID_IRenderable = 0x00000001 , IID_ICollideable = 0x00000002 }; public: bool SupportsInterface (const InterfaceID iid) const { return ((m_iid & iid) != 0); } protected: Entity (const unsigned int iid) : m_iid(iid) {}; private: unsigned int m_iid; }; So, has anyone tried more than one of these approaches and found one to be superior to the other(s) in production code? Are there any other ways to achieve the same results which may be more suitable? Does anyone have any tricks or tips they'd like to share? Cheers, Oscar Cooper. Creature Labs. |