Любопытно повторяющийся шаблон (CRTP), автоматические списки и переполнение стека

Я немного сбит с толку, почему в шаблоне дизайна Curiously Recurring Template Pattern (CRTP) так много «ненависти», например, я читал «Gems Programming Gems 3», и там есть дизайн, называемый autoLists. Это использует CRTP для создания массива объектов каждого типа.

Мой вопрос:

Почему это плохо? Специально нацеленный на идею AutoLists, но ответ о CRTP в целом будет адекватным.

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

3

Решение

Я широко использовал CRTP и некоторые его варианты как в C ++, Java и C #, так и из «отзывов коллег», я могу сказать вам одну вещь: многие люди просто не понимают этого и автоматически становятся враждебными по отношению к таким чрезмерно сложным дерьмо».

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

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

Во-первых, подумайте, действительно ли вам нужно его использовать. Нужно ли базовому классу шаблона точно знать производный тип? Виртуальных членов недостаточно? Можете ли вы уйти без этого? Каковы преимущества в вашем случае? Это сделает «код более высокого уровня» короче, более читабельным, более очевидным или более расширяемым или менее подверженным ошибкам?

Во многих случаях вы обнаружите, что базе не нужно знать точный производный тип, и вы можете заменить его несколькими виртуальными методами. Но это может сделать весь код более сложным для дальнейших пользователей. С другой стороны, с CRTP, последний механизм является более .. «автоматическим», что иногда на самом деле НЕ выгодно.

В случае классов сущностей, часто некоторые варианты CRTP действительно имеют причину: если ваша база предоставляет некоторые служебные методы, которые возвращают «похожие» объекты, вы часто хотите, чтобы эти методы возвращали уточненный «MyObject *», а не «ObjectBase *», и это трудно достичь без него. Но реальный вопрос заключается в следующем: должны ли эти методы действительно находиться в базовом классе сущности, а не внутри «фабрики», «менеджера» или «хранилища контекста»?

4

Другие решения

Наследование в C ++ служит двум различным целям:

  1. Mixins (добавление новый, встраивание поведения в класс, без дублирования кода).
    В этом сценарии базовый класс не имеет собственного значения — его целью является поддержка нового поведения, и не для использования в качестве общего базового класса среди всех подклассов.

  2. Полиморфизм (распространяющийся ужеобъявленный поведение в базовом классе).
    В этом сценарии базовый класс предоставляет общий интерфейс для всех подклассов, yada yada.

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

Иногда вы можете достичь одного и того же с обоими — и разница только в том, является ли «полиморфизм» статическим (во время компиляции) или динамическим (во время выполнения).
Если вам не нужен полиморфизм во время выполнения, тогда вы обычно используете CRTP, потому что он обычно быстрее, так как компилятор может видеть, что происходит во время компиляции.

Тем не менее, CRTP используется достаточно широко, так что я бы не решился сказать, что в нем «так много ненависти».

8

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

#include <iostream>
using namespace std;

class Base {
public:
virtual ~Base() {}
virtual Base *copy() = 0;
virtual void SayHello() = 0;
};

template <typename Derived>
class BaseCopy: public Base {
public:
virtual Base *copy()
{
return new Derived(static_cast<Derived const&>(*this));
}
};

Если пользователь Базового класса не знает об ограниченном использовании и объявляет

class Bar: public BaseCopy<Bar> { public: void SayHello(void) { cout << "Hello, I am Bar\n";} };
class Foo: public BaseCopy<Bar> { public: void SayHello(void) { cout << "Hello, I am Foo\n";} };

int main(void)
{
Foo *foo = new Foo;
Base *foo2 = foo->copy(); // What is foo2?
foo->SayHello();
foo2->SayHello();
delete foo2;
delete foo;

return 0;
}

Компилируя это, например. г ++

g++ -Wall -g main.cpp -o CRTP-test.exe

скомпилирует без проблем но звонит foo->copy(); вызовет неопределенное поведение, так как результатом будет Bar, построенный из Foo.

// Дж.К.

1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector