Когда вы объединяете различные классы в общую структуру, известную только во время выполнения (например, std :: vector здесь), вы не можете избежать издержек виртуального вызова. Это общий принцип C ++, например, его можно найти в std :: function, std :: any и так далее. CRTP ничем не отличается в этом отношении.
Более того, CRTP (также известный как полиморфизм stati c) - это прежде всего инструмент, позволяющий избежать повторения кода (без каких-либо потерь производительности). Он не предназначен для ускорения вызовов виртуальных функций, потому что это dynamici c полиморфизм.
Помимо этого, у вас в основном есть два подхода для настройки ваших классов:
Один из подходов заключается в использовании трехэтапной иерархии наследования, где я считаю полезным разделить динамику c и stati c наследование разными именами функций. Пример:
struct Interface
{
virtual ~Interface() = default;
virtual double do_sthing_impl() = 0;
auto do_sthing() { return this->do_sthing_impl(); }
};
template<typename Derived>
struct Base<Derived> : public Interface
{
virtual double do_sthing_impl() const override { return this->do_sthing(); }
auto do_sthing() { return static_cast<Derived&>(*this).do_sthing(); }
};
struct DerivedA : public Base<DerivedA>
{
auto do_sthing() { /* ... */ }
};
Тогда:
std::vector<std::unique_ptr<Interface> > v;
v.push_back(std::make_unique<DerivedA>());
v.push_back(std::make_unique<DerivedB>());
Второй вариант - type_erasure , например, с помощью следующего кода (который в основном изобретает структуру наследования, но без тесной связи )
template<typename Derived>
struct Base{ /* ... */ };
struct DerivedA : public Base<DerivedA>{ /* ... */ };
struct type_erased
{
virtual double do_sthing() = 0;
};
template<typename Derived>
struct type_erased_impl : public type_erased
{
Derived d;
type_erasure_impl(Derived d) : d(std::move(d)) {}
virtual double do_sthing() const override { return d.do_sthing(); }
};
Тогда:
std::vector<std::unique_ptr<type_erased> > v;
v.push_back(std::make_unique<type_erased_impl<DerivedA> >(DerivedA{}));
v.push_back(std::make_unique<type_erased_impl<DerivedB> >(DerivedB{}));
Я предпочитаю второй вариант, потому что он более гибкий и не вводит фиксированную структуру наследования. Скорее, вы можете реализовать несколько стертых версий одного и того же класса, где вы, в соответствии с реальной потребностью, предоставляете только определенные c функции.