什么是奇怪的重复模板模式(CRTP)?
在不参考书籍的情况下,任何人都可以通过代码示例为CRTP(奇怪的重复模板模式)提供一个很好的解释吗?
Without referring to a book, can anyone please provide a good explanation for CRTP (curiously recurring template pattern) with a code example?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
简而言之,CRTP 是指类
A
具有一个基类,该基类是类A
本身的模板特化。例如,它奇怪地重复出现,不是吗? :)
现在,这给了你什么?这实际上使
X
模板能够成为其专业化的基类。例如,您可以像这样创建一个通用单例类(简化版本)
现在,为了使任意类
A
成为单例,您应该这样做但是,在这种情况下不需要 CRTP,请参阅如下:
看到了吗?单例模板假定其对任何类型
X
的专门化都将从singleton
继承,因此将使其所有(公共、受保护)成员可访问,包括 <代码>获取实例! CRTP 还有其他有用的用途。例如,如果您想要计算类当前存在的所有实例,但想要将此逻辑封装在单独的模板中(具体类的想法非常简单 - 有一个静态变量,以 ctor 为单位递增,以 dtor 为单位递减) )。尝试将其作为练习!还有一个有用的例子,对于 Boost(我不确定他们是如何实现的,但 CRTP 也可以)。
想象一下,您只想为您的类提供运算符
<
,但自动为它们提供运算符==
!你可以这样做:
或者在模板范围内实现而不进行强制转换
现在你可以像这样使用它
现在,你还没有为
Apple
显式提供运算符==
?但你有它!您可以这样写:如果您只为
Apple
编写运算符==
,这似乎会少写一些,但想象一下Equality
模板将提供不仅是==
,还有>
、>=
、<=
等。你可以使用这些定义多个类,重用代码!CRTP 是一件很棒的事情 :) HTH
In short, CRTP is when a class
A
has a base class which is a template specialization for the classA
itself. E.g.It is curiously recurring, isn't it? :)
Now, what does this give you? This actually gives the
X
template the ability to be a base class for its specializations.For example, you could make a generic singleton class (simplified version) like this
Now, in order to make an arbitrary class
A
a singleton you should do thisHowevry, CRTP is not necessary in this case, see as follow:
So you see? The singleton template assumes that its specialization for any type
X
will be inherited fromsingleton<X>
and thus will have all its (public, protected) members accessible, including theGetInstance
! There are other useful uses of CRTP. For example, if you want to count all instances that currently exist for your class, but want to encapsulate this logic in a separate template (the idea for a concrete class is quite simple - have a static variable, increment in ctors, decrement in dtors). Try to do it as an exercise!Yet another useful example, for Boost (I am not sure how they have implemented it, but CRTP will do too).
Imagine you want to provide only operator
<
for your classes but automatically operator==
for them!you could do it like this:
or implement within the template scope without casting
Now you can use it like this
Now, you haven't provided explicitly operator
==
forApple
? But you have it! You can writeThis could seem that you would write less if you just wrote operator
==
forApple
, but imagine that theEquality
template would provide not only==
but>
,>=
,<=
etc. And you could use these definitions for multiple classes, reusing the code!CRTP is a wonderful thing :) HTH
在这里你可以看到一个很好的例子。如果您使用虚拟方法,程序将知道在运行时执行什么。实现CRTP的编译器是在编译时决定的!!!这是一场很棒的表演!
Here you can see a great example. If you use virtual method the program will know what execute in runtime. Implementing CRTP the compiler is which decide in compile time!!! This is a great performance!
CRTP是一种实现编译时多态性的技术。这是一个非常简单的例子。在下面的示例中,
ProcessFoo()
使用Base
类接口,并且Base::Foo
调用派生对象的foo()
方法,这就是您要使用虚拟方法执行的操作。http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e
输出:
CRTP is a technique to implement compile-time polymorphism. Here's a very simple example. In the below example,
ProcessFoo()
is working withBase
class interface andBase::Foo
invokes the derived object'sfoo()
method, which is what you aim to do with virtual methods.http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e
Output:
这不是一个直接的答案,而是 CRTP 如何发挥作用的一个示例。
CRTP 的一个很好的具体示例是 C++11 中的
std::enable_shared_from_this
:也就是说,从
std::enable_shared_from_this
继承可以获取指向实例的共享(或弱)指针,而无需访问它(例如,从您只知道* 的成员函数)这个
)。当您需要提供
std::shared_ptr
但您只能访问*this
时,它很有用:您不能只传递
this
的原因> 直接代替shared_from_this()
的原因是它会破坏所有权机制:This is not a direct answer, but rather an example of how CRTP can be useful.
A good concrete example of CRTP is
std::enable_shared_from_this
from C++11:That is, inheriting from
std::enable_shared_from_this
makes it possible to get a shared (or weak) pointer to your instance without access to it (e.g. from a member function where you only know about*this
).It's useful when you need to give a
std::shared_ptr
but you only have access to*this
:The reason you can't just pass
this
directly instead ofshared_from_this()
is that it would break the ownership mechanism:注意:
CRTP可以用来实现静态多态(类似于动态多态,但没有虚函数指针表)。
输出将是:
Just as note:
CRTP could be used to implement static polymorphism(which like dynamic polymorphism but without virtual function pointer table).
The output would be :
使用 CRTP 的另一个很好的例子是观察者设计模式的实现。一个小例子可以这样构建。
假设您有一个类
date
和一些侦听器类,例如date_drawer
、date_reminder
等。侦听器类(观察者)每当日期发生变化时,主题类
date
(可观察的)应该通知他们,以便他们可以完成他们的工作(在某些情况下绘制日期)格式、提醒特定日期等)。您可以做的是拥有两个应该派生的参数化基类
observer
和observable
您的
date
和观察者类(在我们的例子中为date_drawer
)。观察者设计模式的实现可以参考GOF等经典书籍。这里我们只需要重点介绍 CRTP 的使用。我们来看一下。
在我们的草案实现中,observer 基类有一个纯虚拟方法,每当状态发生变化时,observable 类就应该调用该方法,
我们将此方法称为
state_changed
。我们来看看这个小抽象基类的代码。在这里,我们应该关注的主要参数是第一个参数
T*
,它将是状态更改的对象。第二个参数将是被更改的字段,它可以是任何内容,甚至可以省略它,这不是我们主题的问题(在本例中,它是 std::variant
3 个字段)。
第二个基类
也是一个参数类,它依赖于类型
T*
,并且与传递给内部的state_changed
函数的对象相同。notify_observers
函数。剩下的只是介绍实际的主题类
date
和观察者类date_drawer
。 这里使用了 CRTP 模式,我们从observable
派生出date
observable 类:class date : public observable
.让我们编写一些客户端代码:
该测试程序的输出将是。
请注意,每当发生状态更改时,使用 CRTP 并将
this
传递给通知notify_observers
函数(此处为set_date_properties
和set_date
)。允许我们在实际date_drawer< 中重写
void state_changed(date* c,variantstate)
纯虚函数时使用date*
/code> 观察者类,因此我们里面有date* c
(不是observable*
),例如我们可以调用date* 的非虚函数
(在我们的例子中为get_date
)在
state_changed
函数内。我们可以避免使用 CRTP,因此不参数化观察者设计模式实现,并在任何地方使用可观察的基类指针。这样我们可以达到相同的效果,但在这种情况下,每当我们想要使用派生类指针时(即使不是很推荐),我们都应该使用
dynamic_cast
向下转型,这会产生一些运行时开销。Another good example of using CRTP can be an implementation of observer design pattern. A small example can be constructed like this.
Suppose you have a class
date
and you have some listener classes likedate_drawer
,date_reminder
, etc.. The listener classes (observers)should be notified by the subject class
date
(observable) whenever a date change is done so that they can do their job (draw a date in someformat, remind for a specific date, etc.). What you can do is to have two parametrized base classes
observer
andobservable
from which you should deriveyour
date
and observer classes (date_drawer
in our case). For the observer design pattern implementation refer to the classic books like GOF. Here we only need tohighlight the use of CRTP. Let's look at it.
In our draft implementation
observer
base class has one pure virtual method which should be called by theobservable
class whenever a state change occurred,let's call this method
state_changed
. Let's look at the code of this small abstract base class.Here, the main parameter that we should focus on is the first argument
T*
, which is going to be the object for which a state was changed. The second parameteris going to be the field that was changed, it can be anything, even you can omit it, that's not the problem of our topic (in this case it's a
std::variant
of3 fields).
The second base class is
which is also a parametric class that depends on the type
T*
and that's the same object that is passed to thestate_changed
function inside thenotify_observers
function.Remains only to introduce the actual subject class
date
and observer classdate_drawer
. Here the CRTP pattern is used, we derive thedate
observable class fromobservable<date>
:class date : public observable<date>
.Let's write some client code:
the output of this test program will be.
Note that using CRTP and passing
this
to the notifynotify_observers
function whenever a state change occurred (set_date_properties
andset_date
here). Allowed us to usedate*
when overridingvoid state_changed(date* c, variant<string, int, bool> state)
pure virtual function in the actualdate_drawer
observer class, hence we havedate* c
inside it (notobservable*
) and for example we can call a non-virtual function ofdate*
(get_date
in our case)inside the
state_changed
function.We could of refrain from wanting to use CRTP and hence not parametrizing the observer design pattern implementation and use
observable
base class pointer everywhere. This way we could have the same effect, but in this case whenever we want to use the derived class pointer (even though not very recomendeed) we should usedynamic_cast
downcasting which has some runtime overhead.