Два разных миксиновых паттерна в C ++. (миксин? CRTP?)

Я изучаю миксины (в C ++). Я прочитал несколько статей о миксинах и обнаружил две разные модели «аппроксимирующих» миксинов в C ++.

Образец 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> {
};

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

12

Решение

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

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

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

8

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

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

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

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

Взаимосвязь первых моделей моделей является «is-a» (MyTypeWithMixins это Mixin1<MyType> специализации, Mixin1<MyType> это MyType специализации).

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

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

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

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

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

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

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's 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's 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 сохраните свои настройки с номером версии, но они никоим образом не находятся в одной и той же иерархии объектов (у них нет общей базы).

7

По вопросам рекламы [email protected]