Как я могу избежать виртуальных вызовов при использовании crtp с общим интерфейсным классом? - PullRequest
1 голос
/ 01 апреля 2020

Я использую CRTP из соображений производительности, поэтому у меня есть такая иерархия:

template <typename Derived>
class Base{
  public:
    double do_sthing(){
      static_cast<Derived*>(this)->do_sthing();
      }
};

class DerivedA : public Base<DerivedA>{
  public:
    inline double do_sthing(){ ... }
 };

class DerivedB : public Base<DerivedB>{
  public:
    inline double do_sthing(){ ... }
  };

Но теперь я хочу поместить объекты типа DerivedA и DerivedB в вектор и сделать следующий вид кода:

 for(auto obj : my_vector) {
   obj->do_sthing();
 }

Мое решение состояло в том, чтобы создать общий интерфейсный класс, от которого будет наследоваться Base:

class Interface {
  virtual void do_sthing() = 0;
};

Но в этом случае у меня возникают накладные расходы на виртуальный вызов, и я сделал все CRTP, чтобы избежать этого. Есть ли способ избежать этого? Я думал об объявлении метода do_sthing в Base как окончательного.

template <typename Derived>
class Base{
  public:
  double do_sthing() override final{
    static_cast<Derived*>(this)->do_sthing();
  }
};

Не страдаю ли я из-за стоимости виртуального вызова в приведенном выше? Действительно, если я использую виртуальные методы и обычный старый полиморфизм, мой выигрыш в производительности исходит от поиска vtable, а также от потери inline в случае шаблонов

1 Ответ

0 голосов
/ 01 апреля 2020

Когда вы объединяете различные классы в общую структуру, известную только во время выполнения (например, 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 функции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...