Преимущества CRTP по сравнению с абстрактным классом?

Я новичок в концепции «любопытно повторяющегося шаблона шаблона», и я читаю о его возможных вариантах использования здесь.

В этой статье автор описывает простой случай, когда у нас есть два или более классов с некоторой общей функциональностью:

class A {
public:
  int function getValue() {...}
  void function setValue(int value) {...}

  // Some maths functions
  void scale() {...}
  void square() {...}
  void invert() {...}
}

class B {
public:
  double function getValue() {...}
  void function setValue(double value) {...}

  // Some maths functions
  void scale() {...}
  void square() {...}
  void invert() {...}
}

Автор утверждает, что вместо того, чтобы повторять общую функциональность в каждом классе, мы могли бы вместо этого использовать CRTP:

template <typename T>
struct NumericalFunctions
{
    void scale(double multiplicator);
    void square();
    void invert();
};

class A : public NumericalFunctions<A>
{
public:
    double getValue();
    void setValue(double value);
};

Но чего я не понимаю, так это почему мы не можем просто использовать абстрактный класс для общей функциональности и наследовать от него:

class NumericalFunctions
{
    virtual double function getValue() = 0;
    virtual void function setValue(double value) = 0;
    void scale(double multiplicator){...};
    void square(){...};
    void invert(){...};
};

class A : public NumericalFunctions
{
public:
    double getValue() override;
    void setValue(double value) override;
};

Я не могу придумать никаких преимуществ, которые дает метод CRTP, которых нет у абстрактного класса. Есть ли какие-то преимущества? Метод абстрактного класса кажется мне более простым и позволяет избежать потенциального случая бесполезных сообщений об ошибках компилятора, когда недопустимый тип (тот, который не реализует getValue() или setValue()) передается в качестве параметра шаблона в методе CRTP.


person John O'brien    schedule 16.12.2019    source источник
comment
С помощью crtp вы можете напрямую вызвать функцию параметра шаблона, разрешенную во время компиляции. С виртуальными функциями есть накладные расходы из таблиц виртуальных функций и т. Д.   -  person Yamahari    schedule 16.12.2019


Ответы (1)


Несколько преимуществ использования CRTP:

  • Общий код — базы кода, такие как классы-контейнеры, списки, массивы и т. д. Программы, оцениваемые во время компиляции.
  • Повторное использование – возможность легко использовать существующий код для создания нового кода.
  • Эффективность. В основе универсальной кодовой базы: объекты, вызовы функций, их типы и даже их значения определяются во время компиляции, а не во время выполнения, в отличие от виртуальных методов, которые требуют накладных расходов во время выполнения от сгенерированные компилятором и операционной системой таблицы виртуальных функций.
  • Удобство сопровождения. Легче поддерживать, поскольку обычно требуется меньше кода для управления, в отличие от множественного наследования в сложных иерархиях классов.
  • Надежность. Знание того, что код будет работать для разных типов, не требует беспокойства о внутренней реализации.
  • Читаемость. Назначение кода очевидно, и нет необходимости беспокоиться о его внутренней структуре.


Некоторые недостатки обнаруживаются в их сложности:

  • Синтаксис. Синтаксис более сложен, и его сложно написать правильно, без каких-либо запахов кода.
  • Компилятор. В зависимости от компилятора некоторые сообщения могут быть более загадочными или, если вы не совсем понимаете компилятор, сгенерированную сборку и то, что происходит за кулисами, это может указать вам на неправильную строку код, который фактически создает или вызывает ошибку.
  • Компоновщик. С компоновщиком проблем быть не должно, если код компилируется и объекты компонуются правильно.
  • Отладчик. Подобно компилятору, сообщения могут выглядеть более загадочно и указывать на неправильное место в коде, которое фактически вызывает или инициирует ошибку или исключение.
  • Читаемость — Читать может быть немного сложнее из-за сложности требуемого синтаксиса

Предложение определить, какую идиому лучше всего использовать, состоит в том, чтобы взвесить различия на основе того, что необходимо для конкретной кодовой базы.

  • Если вам нужен быстрый и эффективный код, который можно легко повторно использовать с минимальными затратами, лучше подойдет CRTP.
  • Если вам нужна более мощная кодовая база ООП, в которой используются иерархии классов с наследованием, полиморфизмом, а также использование динамической памяти и памяти в куче, то более поздняя версия подойдет лучше всего.

Тем не менее, одна кодовая база может использовать обе идиомы и использовать их сильные стороны в интегрированном приложении. Это больше о том, чтобы знать, где и когда вам нужен каждый тип.

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

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

В движке 3D-игры; у вас может быть несколько классов-контейнеров, в которых будут храниться все активы игры, такие как файлы изображений, известные как текстуры, шрифты, спрайты, модели, шейдеры, аудио и многое другое. Эти типы классов, которые управляют функциями открытия, чтения и анализа этих файлов и преобразования их информации в поддерживаемые вами пользовательские структуры данных, по своей сути будут CRTP, но они по-прежнему могут использовать общие концепции наследования.

Например, отдельные классы менеджеров сами по себе должны быть разработаны в стиле CRTP для обработки всех операций загрузки файлов, создания, хранения и очистки памяти ваших пользовательских объектов, но большая часть из них может сама по себе наследовать от объекта базового класса Singleton, который может требовать или не требовать, чтобы подклассы имели виртуальные функции. Иерархия классов может выглядеть так:

  • Singleton - An abstract base class the must be inherited from, each of it's derived types can be constructed once per application run. All of the following classes are derived from Singleton.
    • AssetManager - Manages the storing and clean up of internally stored objects either being a texture, font, gui, model, shader, or audio file...
    • AudioManager — управляет всеми функциями воспроизведения аудио.
    • TextureManager — управляет количеством экземпляров различных загруженных текстур, предотвращает ненужное дублирование открытия, чтения и загрузки одного файла нескольких типов, а также предотвращает создание и хранение нескольких копий одного и того же объекта.
    • FontManager — управляет всеми свойствами шрифтов, аналогично TextureManager, но разработанным специально для работы со шрифтами.
    • SpriteManager - В зависимости от движка и типа игры спрайты могут использоваться или не использоваться, или в целом могут считаться текстурой, но определенного типа...
    • ShaderManager — обрабатывает все шейдеры для выполнения операций освещения и затенения в сгенерированном кадре или сцене.
    • GuiManager — обрабатывает все типы объектов графического пользовательского интерфейса, которые поддерживает ваш движок, такие как кнопки, переключатели, ползунки, списки, флажки, текстовые поля, поля макросов и т. д.
    • AnimationManager — будет обрабатывать и хранить все анимации объектов, поддерживаемые вашим движком.
    • TerrainManager – отвечает за все игровые сведения о ландшафте, начиная с вершин и данных нормалей и заканчивая картами высот, рельефными картами и т. д., цветными или узорчатыми текстурами и различными шейдерами, связанными с ландшафтом, которые также могут включать скайбокс или купол, облака, солнце или луна и справочная информация. Может также включать такие объекты, как фольга — трава, деревья, кусты и т. д.
    • ModelManager — обрабатывает всю информацию о 3D-модели, создавая необходимые 3D-сетки для ваших объектов, а также обрабатывает другие внутренние данные, такие как координаты текстуры, координаты индекса и координаты нормали.

Как вы можете видеть выше, каждый из этих классов менеджеров будет разработан с использованием CRTP, поскольку он обеспечивает способ создания универсальных структур, которые могут обрабатывать множество различных типов объектов. Тем не менее, вся иерархия классов по-прежнему использует наследование и может требовать или не требовать виртуальных методов. Последнее зависит от ваших потребностей или намерений. Если вы ожидаете, что кто-то еще будет повторно использовать ваш класс Singleton для реализации своего личного типа менеджера или какого-либо другого класса, который будет Singleton, например Logger и/или ExceptionHandler, вы можете потребовать, чтобы они реализовали Initialization и Cleanup функции.

Теперь, что касается ваших внутриигровых объектов, таких как модели, которые будут использоваться в вашем классе, или даже абстрактных идей, таких как игрок и враги, где они будут наследоваться от общего класса Character, они будут генерировать иерархию классов, которая может требовать или не требовать использование виртуальных методов в зависимости от конкретных потребностей и этих классов не требует идиомы CRTP.

Это должно было показать, когда, где и как правильно использовать эти идиомы.


Если вы хотите увидеть, как отличается производительность между ними, в конечном итоге вы можете написать две базы кода, которые будут выполнять одну и ту же задачу, создать одну специально с CRTP, а другую без нее, используя только Наследование и виртуальные функции. Затем введите эти кодовые базы в один из различных онлайн-компиляторов, которые сгенерируют различные типы инструкций сборки для различных типов доступных компиляторов, которые можно использовать, и сравните сгенерированную сборку. Мне нравится Compiler Explorer, который используется Джейсон Тернер.

person Francis Cugler    schedule 16.12.2019