[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.
|