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