型 这也是一个依赖于类型T*的参数类,并且这是传递给notify_observers函数内部的state_changed函数的同一个对象。仍然只是引入实际的主体类date和观察者类date_drawer。这里使用了CRTP模式,我们从observable<date>导出date可观测类:class date : public observable<date>。
class date : public observable<date>
{
string date_;
int code;
bool is_bank_holiday;
public:
void set_date_properties(int code_ = 0, bool is_bank_holiday_ = false)
{
code = code_;
is_bank_holiday = is_bank_holiday_;
//...
notify_observers(this, code);
notify_observers(this, is_bank_holiday);
}
void set_date(const string& new_date, int code_ = 0, bool is_bank_holiday_ = false)
{
date_ = new_date;
//...
notify_observers(this, new_date);
}
string get_date() const { return date_; }
};
class date_drawer : public observer<date>
{
public:
void state_changed(date* c, variant<string, int, bool> state) override
{
visit([c](const auto& x) {cout << "date_drawer notified, new state is " << x << ", new date is " << c->get_date() << endl; }, state);
}
};
型 让我们写一些客户端代码:
date c;
c.subscribe_observer(make_unique<date_drawer>());
c.set_date("27.01.2022");
c.set_date_properties(7, true);
型 该测试程序的输出将是。
date_drawer notified, new state is 27.01.2022, new date is 27.01.2022
date_drawer notified, new state is 7, new date is 27.01.2022
date_drawer notified, new state is 1, new date is 27.01.2022
6条答案
按热度按时间vsaztqbk1#
简而言之,CRTP是当类
A
有一个基类时,该基类是类A
本身的模板特化。字符串
这是一个奇怪的循环,不是吗?:)
现在,这给了你什么给予呢?这实际上给了
X
模板成为其专门化的基类的能力。例如,您可以像这样创建一个通用的单例类(简化版)
型
现在,为了使任意类
A
成为单例,您应该这样做型
然而,在这种情况下,CRTP是不必要的,见以下:
型
所以你明白了吗?单例模板假定它对任何类型
X
的专门化都将从singleton<X>
继承,因此将具有其所有的(public,protected)成员可访问,包括GetInstance
!CRTP还有其他有用的用途。例如,如果你想计算你的类当前存在的所有示例,但是想把这个逻辑封装在一个单独的模板中(具体类的想法很简单--有一个静态变量,在ctors中递增,在dtors中递减)。另一个有用的例子,对于Boost(我不确定他们是如何实现的,但CRTP也会这样做)。想象一下,你只想为你的类提供运算符
<
,但自动为它们提供运算符==
!你可以这样做:
型
或在模板范围内实现而不进行类型转换
型
现在你可以像这样使用它
型
现在,您还没有为
Apple
显式地提供运算符==
?但是您已经有了!您可以这样写:型
如果你只为
Apple
写操作符==
,这看起来会写得更少,但是想象一下,Equality
模板不仅提供==
,而且还提供>
,>=
,<=
等。CRTP是一个很棒的东西:)HTH
kpbwa7wx2#
在这里你可以看到一个很棒的例子。如果你使用虚方法,程序将知道在运行时执行什么。实现CRTP,编译器在编译时决定哪个!这是一个很棒的性能!
字符串
nimxete23#
CRTP是一种实现编译时多态性的技术。这里有一个非常简单的例子。在下面的例子中,
ProcessFoo()
使用Base
类接口,Base::Foo
调用派生对象的foo()
方法,这就是你想用虚方法做的事情。http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e
字符串
输出量:
型
fjaof16o4#
这不是一个直接的答案,而是一个例子,说明 CRTP 如何有用。
std::enable_shared_from_this
:[util.smartptr.enab]/1
类
T
可以从enable_shared_from_this<T>
继承,以继承获得指向*this
的shared_ptr
示例的shared_from_this
成员函数。也就是说,从
std::enable_shared_from_this
继承可以获得一个指向示例的共享(或弱)指针,而无需访问它(例如,从一个成员函数中,您只知道*this
)。当你需要给予一个
std::shared_ptr
,但你只能访问*this
时,它很有用:字符串
不能直接传递
this
而不是shared_from_this()
的原因是它会破坏所有权机制:型
68de4m5k5#
正如注:
CRTP可以用来实现静态多态(类似于动态多态,但没有虚函数指针表)。
字符串
输出将是:
型
wmomyfyw6#
另一个使用CRTP的好例子是观察者设计模式的实现。
假设你有一个类
date
,你有一些侦听器类,如date_drawer
,date_reminder
等。(观察员)应由主题类date
通知(可观察到的)每当日期更改完成,以便他们可以做他们的工作(以某种格式绘制日期,提醒特定日期,你可以做的是有两个参数化的基类observer
和observable
,你应该从它们派生出你的date
和观察者类(在我们的例子中是date_drawer
)。对于观察者设计模式的实现,请参考像GOF这样的经典书籍。这里我们只需要强调CRTP的使用。让我们来看看它。在我们的草案实现中,observer
基类有一个纯虚方法,每当状态发生变化时,observable
类都应该调用它,让我们把这个方法命名为state_changed
。让我们看看这个小的抽象基类的代码。字符串
这里,我们应该关注的主要参数是第一个参数
T*
,它将是状态被更改的对象。第二个参数将是被更改的字段,它可以是任何东西,甚至你可以省略它,这不是我们主题的问题(在这种情况下,它是3个字段的std::variant
)。第二个基类是型
这也是一个依赖于类型
T*
的参数类,并且这是传递给notify_observers
函数内部的state_changed
函数的同一个对象。仍然只是引入实际的主体类date
和观察者类date_drawer
。这里使用了CRTP模式,我们从observable<date>
导出date
可观测类:class date : public observable<date>
。型
让我们写一些客户端代码:
型
该测试程序的输出将是。
型
请注意,只要发生状态更改,就使用CRTP并将
this
传递给notifynotify_observers
函数(这里是set_date_properties
和set_date
)。允许我们在实际的date_drawer
观察者类中重写void state_changed(date* c, variant<string, int, bool> state)
纯虚函数时使用date*
,因此,我们在其中有date* c
(不是observable*
),例如我们可以调用date*
的非虚函数(get_date
在我们的情况下)在state_changed
函数内部。我们可以避免使用CRTP,因此不需要参数化观察者设计模式的实现和使用observable
基类指针无处不在。这样我们可以有相同的效果,但在这种情况下,每当我们想使用派生类指针(即使不是很推荐),我们应该使用dynamic_cast
向下转换,这会有一些运行时开销。