C++ C++;。(mixin?CRTP?)
我正在学习mixin(在C++中)。我读了一些关于Mixin的文章,发现了C++中“近似”混合的两种不同模式。 模式1: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
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> {};