在没有参考书籍的情况下,任何人都可以用一个代码示例来很好地解释 CRTP(奇怪的重复模板模式)吗?
vsaztqbk1#
简而言之,CRTP是当类A有一个基类时,该基类是类A本身的模板特化。
A
template <class T> class X{...};class A : public X<A> {...};
template <class T>
class X{...};
class A : public X<A> {...};
字符串这是一个奇怪的循环,不是吗?:)现在,这给了你什么给予呢?这实际上给了X模板成为其专门化的基类的能力。例如,您可以像这样创建一个通用的单例类(简化版)
X
#include <iostream>template <class T>class Singleton{public: static T* GetInstance() { if ( p == nullptr ) p = new T(); return p; }protected: Singleton() = default; Singleton(Singleton const &) = delete; Singleton &operator=(const Singleton &) = delete;private: static T *p;};template <class T>T *Singleton<T>::p= nullptr;
#include <iostream>
class Singleton
{
public:
static T* GetInstance() {
if ( p == nullptr ) p = new T();
return p;
}
protected:
Singleton() = default;
Singleton(Singleton const &) = delete;
Singleton &operator=(const Singleton &) = delete;
private:
static T *p;
};
T *Singleton<T>::p= nullptr;
型现在,为了使任意类A成为单例,您应该这样做
class A : public Singleton<A> { friend Singleton;private: A() = default;};A *a0= A::GetInstance();
class A : public Singleton<A>
friend Singleton;
A() = default;
A *a0= A::GetInstance();
型然而,在这种情况下,CRTP是不必要的,见以下:
class C { friend Singleton<C>; private: C() = default;};C *c1= Singleton<C>::GetInstance();
class C
friend Singleton<C>;
private: C() = default;
C *c1= Singleton<C>::GetInstance();
型所以你明白了吗?单例模板假定它对任何类型X的专门化都将从singleton<X>继承,因此将具有其所有的(public,protected)成员可访问,包括GetInstance!CRTP还有其他有用的用途。例如,如果你想计算你的类当前存在的所有示例,但是想把这个逻辑封装在一个单独的模板中(具体类的想法很简单--有一个静态变量,在ctors中递增,在dtors中递减)。另一个有用的例子,对于Boost(我不确定他们是如何实现的,但CRTP也会这样做)。想象一下,你只想为你的类提供运算符<,但自动为它们提供运算符==!你可以这样做:
singleton<X>
GetInstance
<
==
template<class Derived>class Equality{};template <class Derived>bool operator == (Equality<Derived> const& op1, Equality<Derived> const & op2){ Derived const& d1 = static_cast<Derived const&>(op1);//you assume this works //because you know that the dynamic type will actually be your template parameter. //wonderful, isn't it? Derived const& d2 = static_cast<Derived const&>(op2); return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <}
template<class Derived>
class Equality
template <class Derived>
bool operator == (Equality<Derived> const& op1, Equality<Derived> const & op2)
Derived const& d1 = static_cast<Derived const&>(op1);//you assume this works
//because you know that the dynamic type will actually be your template parameter.
//wonderful, isn't it?
Derived const& d2 = static_cast<Derived const&>(op2);
return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <
型或在模板范围内实现而不进行类型转换
template<class T>class Equality{ friend bool operator == (const T& op1, const T& op2) { return !(op1 < op2) && !(op2 < op1); }};
template<class T>
friend bool operator == (const T& op1, const T& op2)
return !(op1 < op2) && !(op2 < op1);
型现在你可以像这样使用它
struct Apple:public Equality<Apple> { int size;};bool operator < (Apple const & a1, Apple const& a2){ return a1.size < a2.size;}
struct Apple:public Equality<Apple>
int size;
bool operator < (Apple const & a1, Apple const& a2)
return a1.size < a2.size;
型现在,您还没有为Apple显式地提供运算符==?但是您已经有了!您可以这样写:
Apple
int main(){ Apple a1; Apple a2; a1.size = 10; a2.size = 10; if(a1 == a2) //the compiler won't complain! { }}
int main()
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
型如果你只为Apple写操作符==,这看起来会写得更少,但是想象一下,Equality模板不仅提供==,而且还提供>,>=,<=等。CRTP是一个很棒的东西:)HTH
Equality
>
>=
<=
kpbwa7wx2#
在这里你可以看到一个很棒的例子。如果你使用虚方法,程序将知道在运行时执行什么。实现CRTP,编译器在编译时决定哪个!这是一个很棒的性能!
template <class T>class Writer{ public: Writer() { } ~Writer() { } void write(const char* str) const { static_cast<const T*>(this)->writeImpl(str); //here the magic is!!! }};class FileWriter : public Writer<FileWriter>{ public: FileWriter(FILE* aFile) { mFile = aFile; } ~FileWriter() { fclose(mFile); } //here comes the implementation of the write method on the subclass void writeImpl(const char* str) const { fprintf(mFile, "%s\n", str); } private: FILE* mFile;};class ConsoleWriter : public Writer<ConsoleWriter>{ public: ConsoleWriter() { } ~ConsoleWriter() { } void writeImpl(const char* str) const { printf("%s\n", str); }};
class Writer
Writer() { }
~Writer() { }
void write(const char* str) const
static_cast<const T*>(this)->writeImpl(str); //here the magic is!!!
class FileWriter : public Writer<FileWriter>
FileWriter(FILE* aFile) { mFile = aFile; }
~FileWriter() { fclose(mFile); }
//here comes the implementation of the write method on the subclass
void writeImpl(const char* str) const
fprintf(mFile, "%s\n", str);
FILE* mFile;
class ConsoleWriter : public Writer<ConsoleWriter>
ConsoleWriter() { }
~ConsoleWriter() { }
printf("%s\n", str);
字符串
nimxete23#
CRTP是一种实现编译时多态性的技术。这里有一个非常简单的例子。在下面的例子中,ProcessFoo()使用Base类接口,Base::Foo调用派生对象的foo()方法,这就是你想用虚方法做的事情。http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e
ProcessFoo()
Base
Base::Foo
foo()
template <typename T>struct Base { void foo() { (static_cast<T*>(this))->foo(); }};struct Derived : public Base<Derived> { void foo() { cout << "derived foo" << endl; }};struct AnotherDerived : public Base<AnotherDerived> { void foo() { cout << "AnotherDerived foo" << endl; }};template<typename T>void ProcessFoo(Base<T>* b) { b->foo();}int main(){ Derived d1; AnotherDerived d2; ProcessFoo(&d1); ProcessFoo(&d2); return 0;}
template <typename T>
struct Base {
void foo() {
(static_cast<T*>(this))->foo();
struct Derived : public Base<Derived> {
cout << "derived foo" << endl;
struct AnotherDerived : public Base<AnotherDerived> {
cout << "AnotherDerived foo" << endl;
template<typename T>
void ProcessFoo(Base<T>* b) {
b->foo();
Derived d1;
AnotherDerived d2;
ProcessFoo(&d1);
ProcessFoo(&d2);
return 0;
字符串输出量:
derived fooAnotherDerived foo
derived foo
AnotherDerived foo
型
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时,它很有用:
T
enable_shared_from_this<T>
*this
shared_ptr
shared_from_this
std::shared_ptr
struct Node;void process_node(const std::shared_ptr<Node> &);struct Node : std::enable_shared_from_this<Node> // CRTP{ std::weak_ptr<Node> parent; std::vector<std::shared_ptr<Node>> children; void add_child(std::shared_ptr<Node> child) { process_node(shared_from_this()); // Shouldn't pass `this` directly. child->parent = weak_from_this(); // Ditto. children.push_back(std::move(child)); }};
struct Node;
void process_node(const std::shared_ptr<Node> &);
struct Node : std::enable_shared_from_this<Node> // CRTP
std::weak_ptr<Node> parent;
std::vector<std::shared_ptr<Node>> children;
void add_child(std::shared_ptr<Node> child)
process_node(shared_from_this()); // Shouldn't pass `this` directly.
child->parent = weak_from_this(); // Ditto.
children.push_back(std::move(child));
字符串不能直接传递this而不是shared_from_this()的原因是它会破坏所有权机制:
this
shared_from_this()
struct S{ std::shared_ptr<S> get_shared() const { return std::shared_ptr<S>(this); }};// Both shared_ptr think they're the only owner of S.// This invokes UB (double-free).std::shared_ptr<S> s1 = std::make_shared<S>();std::shared_ptr<S> s2 = s1->get_shared();assert(s2.use_count() == 1);
struct S
std::shared_ptr<S> get_shared() const { return std::shared_ptr<S>(this); }
// Both shared_ptr think they're the only owner of S.
// This invokes UB (double-free).
std::shared_ptr<S> s1 = std::make_shared<S>();
std::shared_ptr<S> s2 = s1->get_shared();
assert(s2.use_count() == 1);
68de4m5k5#
正如注:CRTP可以用来实现静态多态(类似于动态多态,但没有虚函数指针表)。
#pragma once#include <iostream>template <typename T>class Base{ public: void method() { static_cast<T*>(this)->method(); }};class Derived1 : public Base<Derived1>{ public: void method() { std::cout << "Derived1 method" << std::endl; }};class Derived2 : public Base<Derived2>{ public: void method() { std::cout << "Derived2 method" << std::endl; }};#include "crtp.h"int main(){ Derived1 d1; Derived2 d2; d1.method(); d2.method(); return 0;}
#pragma once
class Base
void method() {
static_cast<T*>(this)->method();
class Derived1 : public Base<Derived1>
std::cout << "Derived1 method" << std::endl;
class Derived2 : public Base<Derived2>
std::cout << "Derived2 method" << std::endl;
#include "crtp.h"
Derived1 d1;
Derived2 d2;
d1.method();
d2.method();
字符串输出将是:
Derived1 methodDerived2 method
Derived1 method
Derived2 method
wmomyfyw6#
另一个使用CRTP的好例子是观察者设计模式的实现。假设你有一个类date,你有一些侦听器类,如date_drawer,date_reminder等。(观察员)应由主题类date通知(可观察到的)每当日期更改完成,以便他们可以做他们的工作(以某种格式绘制日期,提醒特定日期,你可以做的是有两个参数化的基类observer和observable,你应该从它们派生出你的date和观察者类(在我们的例子中是date_drawer)。对于观察者设计模式的实现,请参考像GOF这样的经典书籍。这里我们只需要强调CRTP的使用。让我们来看看它。在我们的草案实现中,observer基类有一个纯虚方法,每当状态发生变化时,observable类都应该调用它,让我们把这个方法命名为state_changed。让我们看看这个小的抽象基类的代码。
date
date_drawer
date_reminder
observer
observable
state_changed
template <typename T>struct observer{ virtual void state_changed(T*, variant<string, int, bool>) = 0; virtual ~observer() {}};
struct observer
virtual void state_changed(T*, variant<string, int, bool>) = 0;
virtual ~observer() {}
字符串这里,我们应该关注的主要参数是第一个参数T*,它将是状态被更改的对象。第二个参数将是被更改的字段,它可以是任何东西,甚至你可以省略它,这不是我们主题的问题(在这种情况下,它是3个字段的std::variant)。第二个基类是
T*
std::variant
template <typename T>class observable{ vector<unique_ptr<observer<T>>> observers;protected: void notify_observers(T* changed_obj, variant<string, int, bool> changed_state) { for (unique_ptr<observer<T>>& o : observers) { o->state_changed(changed_obj, changed_state); } }public: void subscribe_observer(unique_ptr<observer<T>> o) { observers.push_back(move(o)); } void unsubscribe_observer(unique_ptr<observer<T>> o) { }};
class observable
vector<unique_ptr<observer<T>>> observers;
void notify_observers(T* changed_obj, variant<string, int, bool> changed_state)
for (unique_ptr<observer<T>>& o : observers)
o->state_changed(changed_obj, changed_state);
void subscribe_observer(unique_ptr<observer<T>> o)
observers.push_back(move(o));
void unsubscribe_observer(unique_ptr<observer<T>> o)
型这也是一个依赖于类型T*的参数类,并且这是传递给notify_observers函数内部的state_changed函数的同一个对象。仍然只是引入实际的主体类date和观察者类date_drawer。这里使用了CRTP模式,我们从observable<date>导出date可观测类:class date : public observable<date>。
notify_observers
observable<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); }};
string date_;
int code;
bool is_bank_holiday;
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>
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 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.2022date_drawer notified, new state is 7, new date is 27.01.2022date_drawer notified, new state is 1, new date is 27.01.2022
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
型请注意,只要发生状态更改,就使用CRTP并将this传递给notify notify_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向下转换,这会有一些运行时开销。
set_date_properties
set_date
void state_changed(date* c, variant<string, int, bool> state)
date*
date* c
observable*
get_date
dynamic_cast
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
向下转换,这会有一些运行时开销。