Два разных шаблона mixin в С++. (смесь? CRTP?)

Я изучаю mixins (в С++). Я прочитал несколько статей о mixins и нашел два разных шаблона "аппроксимирующих" mixins в С++.

Образец 1:

template<class Base>
struct Mixin1 : public Base {
};

template<class Base>
struct Mixin2 : public Base {
};

struct MyType {
};

typedef Mixin2<Mixin1<MyType>> MyTypeWithMixins;

Образец 2: (можно назвать CRTP)

template<class T>
struct Mixin1 {
};

template<class T>
struct Mixin2 {
};

struct MyType {
};

struct MyTypeWithMixins : 
    public MyType, 
    public Mixin1<MyTypeWithMixins>, 
    public Mixin2<MyTypeWithMixins> {
};

Насколько они эквивалентны? Я хотел бы знать практическую разницу между шаблонами.

Ответ 1

Разница - видимость. В первом шаблоне члены MyType видны и доступны для миксинов, без необходимости кастинга, а члены Mixin1 видны Mixin2. Если MyType хочет получить доступ к элементам из миксинов, ему необходимо указать this, и нет хорошего способа сделать это безопасно.

Во втором шаблоне нет автоматического видимости между типом и микстинами, но микшины могут безопасно и легко лить this в MyTypeWithMixins и тем самым получить доступ к членам типа и других микшинов. (MyType тоже может быть, если вы применили CRTP к нему тоже.)

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

Ответ 2

Насколько они эквивалентны? Я хотел бы знать практическую разницу между шаблонами.

Они различны концептуально.

Для первого шаблона у вас есть декораторы, проходящие (прозрачно) по основному классу функциональности, каждый из которых добавляет свою собственную завихрение/специализацию к существующей реализации.

Отношение первых моделей шаблонов: "is-a" (MyTypeWithMixins - это специализация Mixin1<MyType>, Mixin1<MyType> - это специализация MyType).

Это хороший подход, когда вы реализуете функциональность в жестком интерфейсе (поскольку все типы будут реализовывать один и тот же интерфейс).

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

Взаимосвязь, смоделированная здесь, "реализована в терминах" (MyTypeWithMixins - это специализация MyType, реализованная в терминах Mixin1 и Mixin2). Во многих реализациях CRTP база шаблонов CRTP наследуется как конфиденциальная или защищенная.

Это хороший подход, когда вы реализуете общую функциональность по сравнению с другими несвязанными компонентами (т.е. не с тем же интерфейсом). Это связано с тем, что два класса, наследующие от Mixin1, не будут иметь одинаковый базовый класс.

Чтобы предоставить конкретный пример для каждого:

В первом случае рассмотрим моделирование библиотеки GUI. Каждый визуальный контроль будет иметь (например) display функцию, которая в ScrollableMixin добавит полосы прокрутки, если это необходимо; Промежуточная комбинация прокрутки будет базовым классом для большинства элементов управления, которые являются повторно значимыми (но все они являются частью иерархии классов "контроль/визуальный компонент/отображаемый".

class control {
    virtual void display(context& ctx) = 0;
    virtual some_size_type display_size() = 0;
};

template<typename C>class scrollable<C>: public C { // knows/implements C API
    virtual void display(context& ctx) override {
        if(C::display_size() > display_size())
            display_with_scrollbars(ctx);
        else
            C::display(canvas);
    }
    ... 
};

using scrollable_messagebox = scrollable<messagebox>;

В этом случае все типы микширования будут переопределять (например) метод отображения и делегировать часть его функциональности (специализированной части чертежа) в декорированный тип (базовый).

Во втором случае рассмотрим случай, когда вы будете внедрять внутреннюю систему для добавления номера версии в сериализованные объекты в приложении. Реализация будет выглядеть так:

template<typename T>class versionable<T> { // doesn't know/need T API
    version_type version_;
protected:
    version_type& get_version();
};

class database_query: protected versionable<database_query> {};
class user_information: protected versionable<user_information> {};

В этом случае оба database_query и user_information сохраняют свои настройки с номером версии, но они никоим образом не находятся в одной и той же иерархии объектов (у них нет общей базы).