C++中的两种不同的mixin模式.(mixin?CRTP?)

upx*_*xuk 13 c++ design-patterns mixins crtp

我正在研究mixins(用C++编写).我阅读了一些关于mixins的文章,并在C++中发现了两种不同的"近似"mixins模式.

模式1:

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

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

struct MyType {
};

typedef Mixin2<Mixin1<MyType>> MyTypeWithMixins;
Run Code Online (Sandbox Code Playgroud)

模式2 :(可称为CRTP)

template<class T>
struct Mixin1 {
};

template<class T>
struct Mixin2 {
};

struct MyType {
};

struct MyTypeWithMixins : 
    public MyType, 
    public Mixin1<MyTypeWithMixins>, 
    public Mixin2<MyTypeWithMixins> {
};
Run Code Online (Sandbox Code Playgroud)

它们实际上相当吗?我想知道模式之间的实际差异.

Sne*_*tel 8

不同之处在于可见度.在第一种模式中,其MyType成员直接可见并可由mixin使用,无需任何铸造,并且其Mixin1成员可见Mixin2.如果MyType想要从mixin访问成员,它需要强制转换this,并且没有一个很好的方法可以安全地这样做.

在第二种模式中,在类型和混合物之间没有自动可见性,但是混合物可以安全且容易地投射thisMyTypeWithMixins该类型和其他混合物的成员上.(MyType也可以,如果你也应用了CRTP.)

所以它归结为方便性和灵活性.如果你的mixins纯粹是从类型中访问服务,并且没有自己的兄弟依赖,那么第一个模式就很好而且简单明了.如果mixin依赖于类型或其他mixin提供的服务,那么您或多或少会被迫使用第二种模式.


utn*_*tim 7

它们实际上相当吗?我想知道模式之间的实际差异.

它们在概念上是不同的.

对于第一个模式,您有装饰器(透明地)在核心功能类上运行,每个装饰器都将自己的扭曲/特化添加到现有实现中.

第一种模式模型的关系是"is-a"(MyTypeWithMixins是一种Mixin1<MyType>专业化,Mixin1<MyType>是一种MyType专业化).

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

对于第二种模式,您将功能部件用作实现细节(可能在不同的,不相关的类中).

这里建模的关系以"来讲是实现"(MyTypeWithMixins是一个MyType专业化,在以下方面实现 Mixin1Mixin2功能).在许多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>;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,所有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> {};
Run Code Online (Sandbox Code Playgroud)

在这种情况下,无论是database_queryuser_information存储他们设置了版本号,但他们是在同一个对象的层次结构没有办法(他们没有一个共同的基础).