If your program knows about all the sub types that will be tested against, you can use a virtual interface that returns a pointer to the sub type. As noted by downvotes and comments, this is not the most flexible approach, since it requires the base class have knowledge of all the derived classes. However, it is very fast. So there is a trade off of flexibility to performance.
class Base {
//...
virtual A * is_A () { return 0; }
virtual B * is_B () { return 0; }
//...
template <typename MAYBE_DERIVED>
MAYBE_DERIVED * isTypeOrSubtype () {
//...dispatch to template specialization to call is_X()
}
};
//...
class A : virtual public Base {
//...
A * is_A () { return this; }
};
On IDEONE, the suggested technique is 20 to 50 times faster than using dynamic cast.1 The implementation uses macros to allow a new class to be added to a single place, and the proper expansions to the base class occur in an automated way after that.
(1) - I originally clocked it closer to 100 times as fast, but this was without the isTypeOrSubtype()
wrapper method that I added to simulate the desired interface.
If flexibility has a higher value than performance, then a slightly less performant solution is to use a map
to associate types and corresponding pointer values (having the pointer values removes the need for a dynamic cast). The map
instance is maintained in the base class, and the associations are made by the constructors of the subclasses. Whether a regular map
or a unordered_map
is used will depend on how many subclasses virtually inherit the base class. I would presume the numbers will be small, so a regular map
should suffice.
class Base {
std::map<const char *, void *> children_;
//...
template <typename MAYBE_DERIVED>
MAYBE_DERIVED * isTypeOrSubtype () {
auto x = children_.find(typeid(MAYBE_DERIVED).name());
return ((x != children_.end())
? static_cast<MAYBE_DERIVED *>(x->second)
: 0);
}
};
//...
class A : virtual public Base {
//...
A () { children_[typeid(A).name()] = this; }
//...
};
On IDEONE, this second suggestion is 10 to 30 times faster the using dynamic cast. I don't think IDEONE compiles with optimizations, so I would expect the times to be closer to the first suggestion on a production build. The mechanism as implemented uses typeid(...).name()
as the key to the map.2
(2) - This assumes that typeid(...).name()
returns something similar to a string literal, and always returns the same string pointer when operating on the same type. If your system does not behave that way, you can modify the map
to take a std::string
as the key instead, but performance will be degraded.