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

时间:2014-10-21 12:13:42

标签: 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;

模式2 :(可称为CRTP)

template<class T>
struct Mixin1 {
};

template<class T>
struct Mixin2 {
};

struct MyType {
};

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

它们实际上是等同的吗?我想知道模式之间的实际差异。

2 个答案:

答案 0 :(得分:8)

区别在于可见度。在第一种模式中,MyType的成员直接可见并可由mixin使用,无需任何转换,Mixin1的成员对Mixin2可见。如果MyType想要从mixin访问成员,则需要投射this,并且没有一种安全的方法。

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

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

答案 1 :(得分:7)

  

它们实际上是等同的吗?我想知道模式之间的实际差异。

它们在概念上是不同的。

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

第一个模式模型的关系是&#34;是-a&#34; (MyTypeWithMixinsMixin1<MyType>专精,Mixin1<MyType>MyType专业化。

当您在严格的界面中实现功能时,这是一种很好的方法(因为所有类型将实现相同的界面)。

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

这里建模的关系是&#34;是根据&#34; (MyTypeWithMixins是一个MyType专门化,是根据 Mixin1Mixin2功能实现的。在许多CRTP实现中,CRTP模板化基础被继承为私有或受保护。

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

为每个人提供一个具体的例子:

对于第一种情况,请考虑GUI库的建模。每个可视控件都有一个(例如)display函数,如果需要,它可以在ScrollableMixin中添加滚动条;滚动条mixin将是大多数可重新调整大小的控件的基类(但它们都是&#34;控件/可视组件/可显示&#34;类层次结构的一部分。

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>;

在这种情况下,所有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> {};

在这种情况下,database_queryuser_information都会将其设置存储为版本号,但它们不在同一对象层次结构中(它们没有共同的基础)