CRTP, чтобы избежать динамического полиморфизма

Как я могу использовать CRTP в С++, чтобы избежать накладных расходов на действия виртуальных членов?

Ответ 1

Есть два способа.

Первый из них - это указание интерфейса статически для структуры типов:

template <class Derived>
struct base {
  void foo() {
    static_cast<Derived *>(this)->foo();
  };
};

struct my_type : base<my_type> {
  void foo(); // required to compile.
};

struct your_type : base<your_type> {
  void foo(); // required to compile.
};

Во-вторых, избегайте использования ссылки на базовую или указатель-на-базу и выполняйте проводку во время компиляции. Используя приведенное выше определение, вы можете иметь функции шаблонов, которые выглядят так:

template <class T> // T is deduced at compile-time
void bar(base<T> & obj) {
  obj.foo(); // will do static dispatch
}

struct not_derived_from_base { }; // notice, not derived from base

// ...
my_type my_instance;
your_type your_instance;
not_derived_from_base invalid_instance;
bar(my_instance); // will call my_instance.foo()
bar(your_instance); // will call your_instance.foo()
bar(invalid_instance); // compile error, cannot deduce correct overload

Таким образом, объединение определения структуры/интерфейса и вычета типа времени компиляции в ваших функциях позволяет выполнять статическую диспетчеризацию вместо динамической отправки. В этом суть статического полиморфизма.

Ответ 2

Я искал достойные обсуждения CRTP. Todd Veldhuizen Методы для Scientific С++ - отличный ресурс для этого (1.3) и многих других передовых методов, таких как шаблоны выражений.

Кроме того, я обнаружил, что вы можете прочитать большую часть оригинальной статьи Coplien С++ Gems в книгах Google. Может быть, это все еще так.

Ответ 3

Мне пришлось искать CRTP. Однако, сделав это, я нашел кое-что из Статический полиморфизм. Я подозреваю, что это ответ на ваш вопрос.

Оказывается, что ATL использует этот шаблон довольно широко.

Ответ 4

Это В ответе Википедии есть все, что вам нужно. А именно:

template <class Derived> struct Base
{
    void interface()
    {
        // ...
        static_cast<Derived*>(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        Derived::static_sub_func();
        // ...
    }
};

struct Derived : Base<Derived>
{
    void implementation();
    static void static_sub_func();
};

Хотя я не знаю, сколько это фактически покупает вас. Накладные расходы на вызов виртуальной функции (конечно, зависит от компилятора):

  • Память: один указатель на каждую виртуальную функцию
  • Время выполнения: один вызов указателя на функцию

В то время как накладные расходы на статический полиморфизм CRTP:

  • Память: дублирование базы для каждого экземпляра шаблона
  • Время выполнения: один вызов указателя на функцию + независимо от того, что делает static_cast