C++ C++;。(mixin?CRTP?)

C++ C++;。(mixin?CRTP?),c++,design-patterns,mixins,crtp,C++,Design Patterns,Mixins,Crtp,我正在学习mixin(在C++中)。我读了一些关于Mixin的文章,发现了C++中“近似”混合的两种不同模式。 模式1: template<class Base> struct Mixin1 : public Base { }; template<class Base> struct Mixin2 : public Base { }; struct MyType { }; typedef Mixin2<Mixin1<MyType>> MyTy

我正在学习mixin(在C++中)。我读了一些关于Mixin的文章,发现了C++中“近似”混合的两种不同模式。 模式1:

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

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

struct MyType {
};

typedef Mixin2<Mixin1<MyType>> MyTypeWithMixins;
模板
结构Mixin1:公共基{
};
模板
结构Mixin2:公共基{
};
结构MyType{
};
typedef Mixin2 mytypewithmixin;
模式2:(可称为CRTP)

模板
结构Mixin1{
};
模板
结构混合2{
};
结构MyType{
};
结构MyTypeWithMixins:
公共MyType,
公共混合1,
公共混合器2{
};

它们实际上相等吗?我想知道模式之间的实际区别。

区别在于可见性。在第一种模式中,
MyType
的成员对mixin直接可见并可由mixin使用,无需强制转换,
Mixin1
的成员对
Mixin2
可见。如果
MyType
想要从mixin访问成员,它需要强制转换
this
,并且没有一种很好的安全方法

在第二种模式中,类型和mixin之间没有自动可见性,但是mixin可以安全、轻松地将
this
转换为
mytypewithmixin
,从而访问类型和其他mixin的成员。(
MyType
也可以,如果您也对其应用了CRTP的话。)

因此,它归结为方便性与灵活性。如果您的mixin纯粹是从该类型访问服务,并且没有自己的兄弟依赖项,那么第一种模式很好而且简单。如果一个mixin依赖于该类型或其他mixin提供的服务,那么您或多或少会被迫使用第二种模式

它们实际上相等吗?我想知道这两种模式的实际区别

它们在概念上是不同的

对于第一种模式,装饰器(透明地)覆盖核心功能类,每个装饰器都向现有实现添加自己的扭曲/专门化

第一个模式模型的关系是“is-a”(
MyTypeWithMixins
Mixin1
专门化,
Mixin1
MyType
专门化)

在刚性接口中实现功能时,这是一种很好的方法(因为所有类型都将实现相同的接口

对于第二种模式,您可以使用功能部分作为实现细节(可能在不同的、不相关的类中)

此处建模的关系是“根据实现”(
MyTypeWithMixins
是一种
MyType
专门化,根据
Mixin1
Mixin2
功能实现)。在许多CRTP实现中,CRTP模板基作为私有或受保护继承

当您跨不同的、不相关的组件(即不使用相同的接口)实现公共功能时,这是一种很好的方法。这是因为从Mixin1继承的两个类将不具有相同的基类

为每一项提供一个具体示例:

对于第一种情况,考虑GUI库的建模。每个视觉控件都有一个(例如)

显示
功能,如果需要,它在ScrollableMixin中会添加滚动条;scrollbars mixin将是大多数可重新调整大小的控件的基类(但它们都是“control/VisualComponent/displayable”类层次结构的一部分)

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>;
类控制{
虚拟空白显示(上下文和ctx)=0;
虚拟某些大小类型显示大小()=0;
};
templateclass可滚动:公共C{///知道/实现C的API
虚拟无效显示(上下文和ctx)覆盖{
如果(C::display\u size()>display\u size())
显示带有滚动条的滚动条(ctx);
其他的
C::显示(画布);
}
... 
};
使用可滚动的消息框=可滚动;
在这种情况下,所有mixin类型都将覆盖(例如)显示方法,并将其功能的一部分(专用绘图部分)委托给装饰类型(基础)

对于第二种情况,考虑一种情况,当您实现一个内部系统来向应用程序中的序列化对象添加版本号时,该实现将是这样的:

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> {};
templateclass可版本化{//不知道/不需要它的API
版本类型版本;
受保护的:
版本类型&获取版本();
};
类数据库_查询:受保护的可版本{};
类用户信息:受保护的可版本{};

在这种情况下,
database\u query
user\u information
都使用版本号存储它们的设置,但它们绝不在同一个对象层次结构中(它们没有共同的基础).

这不是CRTP的目的。当然,CRTP涉及一般结构
类Child:Parent
,但关键是基类--
Parent
,知道子类
子类
(如何?它是一个模板参数!)然后,父类可以引用在子类本身中定义的东西——例如,它可以从子类的运算符==。@HWalters创建一个运算符!=不一定。在我上面给出的示例中,
versionable
的实现没有对
t
施加任何限制(也就是说,它不需要知道任何有关它的信息)。这是故意的。我错了;使用
Child
来实例化
Parent
只是作为基类型的方便的uniquifier具有实用性。但我认为您认为错误的一点——不对
T
施加限制不足以证明使用CRTP是正确的。特别是,我很难理解您的示例;结尾是结果是
database\u query
对象和
user\u information
对象具有版本和获取版本成员,甚至具有相同的类型。那么使用CRTP注入它们有什么好处呢?(当然,基类
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> {};