I found the following code in some project:
std::vector<Base*> objs_;
template <class TDerived>
T* Get() {
auto objIt = std::find_if(objs_.cbegin(), objs_.cend(), [](Base* it) {
return typeid(TDerived).name() == typeid(*it).name();
});
return objIt == objs_.cend() ? nullptr : *objIt;
}
I'm trying to figure out, is using typeid
a good choice?
Because C++ has dynamic_cast
, for example:
template <class TDerived>
T* Get() {
auto objIt = std::find_if(objs_.cbegin(), objs_.cend(), [](Base* it) {
return dynamic_cast<T*>(it) != nullptr;
});
return objIt == objs_.cend() ? nullptr : *objIt;
}
I know that there is a semantic difference between the solutions above, because dynamic_cast
will complain about TDerived
inheritor also, but that's ok, all used TDerived
are final
.
Is typeid
a good choice, or is dynamic_cast
better, or is there some better solution for it?
No, typeid
is not a good idea at all, because subtyping one of the involved types would require to enrich the parts where typeid
is checked. This is agains the open/closed principle.
By the way, there are a lot of subtle issues with typeid
, e.g. there's no standardization of the type names returned, and moreover, as pointed out on cppreference:
There is no guarantee that the same std::type_info instance will be referred to by all evaluations of the typeid expression on the same type, although they would compare equal, std::type_info::hash_code of those type_info objects would be identical, as would be their std::type_index.
The use of dynamic_cast
is slightly better, because it may allow to respect the open/closed principle regarding if you don't add a new type. But if you do, you're again bound to enrich the code. Moreover, dynamic_cast
requires the using class/function to know about a lot about of the used classes. This may weaken encapsulation and create hidden coupling (i.e you can no longer change the used classes as you want, because you light break some assumptions)
The best approach is to rewrite the code in a polymorphic way. The C++ core guidelines remind in this regard that virtual functions should be preferred to casting. More generally, the approach should use the tell don't ask principle, and let polymorphic code do what it has to do. Or opt for the visitor pattern.