Finding derived object in a vector of base

91 Views Asked by At

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?

2

There are 2 best solutions below

0
Christophe On BEST ANSWER

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.

0
Artyer On

You can't compare the names, because they don't have to have the same pointer values for the same types. For example, typeid(TDerived).name() == typeid(TDerived).name() can be false.

You should be comparing the typeids directly instead with std::type_info::operator==:

    return typeid(TDerived) == typeid(*it);

As for whether this is better than the dynamic_cast, it depends. For a final class as you've mentioned, there is no difference semantically. I would think comparing typeids makes your intentions clearer.