在不参考书籍的情况下,谁能用一个代码示例很好地解释CRTP ?
当前回答
简而言之,CRTP是当一个类a有一个基类,这个基类是类a本身的模板特化。如。
template <class T>
class X{...};
class A : public X<A> {...};
奇怪的是,它反复出现,不是吗?:)
现在,这给了你什么?这实际上使X模板能够成为其专门化的基类。
例如,您可以像这样创建一个泛型单例类(简化版)
template <class ActualClass>
class Singleton
{
public:
static ActualClass& GetInstance()
{
if(p == nullptr)
p = new ActualClass;
return *p;
}
protected:
static ActualClass* p;
private:
Singleton(){}
Singleton(Singleton const &);
Singleton& operator = (Singleton const &);
};
template <class T>
T* Singleton<T>::p = nullptr;
现在,为了让任意的A类成为单例,你应该这样做
class A: public Singleton<A>
{
//Rest of functionality for class A
};
So you see? The singleton template assumes that its specialization for any type X will be inherited from singleton<X> and thus will have all its (public, protected) members accessible, including the GetInstance! 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!
另一个有用的例子是Boost(我不确定他们是如何实现的,但CRTP也可以)。 假设您只想为类提供operator <,而为它们自动提供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 <
}
现在你可以这样使用它
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
现在,你还没有为Apple显式提供operator ==吗?但是你有!你可以写
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
如果只为Apple编写operator ==,似乎会写得更少,但想象一下equal模板不仅提供==,还提供>,>=,<=等。您可以将这些定义用于多个类,从而重用代码!
CRTP是一个很棒的东西:)HTH
其他回答
简而言之,CRTP是当一个类a有一个基类,这个基类是类a本身的模板特化。如。
template <class T>
class X{...};
class A : public X<A> {...};
奇怪的是,它反复出现,不是吗?:)
现在,这给了你什么?这实际上使X模板能够成为其专门化的基类。
例如,您可以像这样创建一个泛型单例类(简化版)
template <class ActualClass>
class Singleton
{
public:
static ActualClass& GetInstance()
{
if(p == nullptr)
p = new ActualClass;
return *p;
}
protected:
static ActualClass* p;
private:
Singleton(){}
Singleton(Singleton const &);
Singleton& operator = (Singleton const &);
};
template <class T>
T* Singleton<T>::p = nullptr;
现在,为了让任意的A类成为单例,你应该这样做
class A: public Singleton<A>
{
//Rest of functionality for class A
};
So you see? The singleton template assumes that its specialization for any type X will be inherited from singleton<X> and thus will have all its (public, protected) members accessible, including the GetInstance! 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!
另一个有用的例子是Boost(我不确定他们是如何实现的,但CRTP也可以)。 假设您只想为类提供operator <,而为它们自动提供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 <
}
现在你可以这样使用它
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
现在,你还没有为Apple显式提供operator ==吗?但是你有!你可以写
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
如果只为Apple编写operator ==,似乎会写得更少,但想象一下equal模板不仅提供==,还提供>,>=,<=等。您可以将这些定义用于多个类,从而重用代码!
CRTP是一个很棒的东西:)HTH
使用CRTP的另一个好例子是观察者设计模式的实现。可以像这样构造一个小示例。
Suppose you have a class date and you have some listener classes like date_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 some format, remind for a specific date, etc.). What you can do is to have two parametrized base classes observer and observable from which you should derive your 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 to highlight 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 the observable 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.
template <typename T>
struct observer
{
virtual void state_changed(T*, variant<string, int, bool>) = 0;
virtual ~observer() {}
};
这里,我们应该关注的主要参数是第一个参数T*,它将是一个状态被改变的对象。第二个参数 将是被改变的字段,它可以是任何东西,甚至你可以省略它,这不是我们的主题的问题(在这种情况下,它是std::的变体 3字段)。 第二个基类是
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)
{
}
};
它也是一个参数类,依赖于类型T*,这是同一个对象,传递给state_changed函数在 notify_observers函数。 只需要介绍实际的主题类date和观察者类date_drawer。这里使用CRTP模式,我们从observable<date>: class date: public observable<date>派生出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
Note that using CRTP and passing this to the notify notify_observers function whenever a state change occurred (set_date_properties and set_date here). Allowed us to use date* when overriding void state_changed(date* c, variant<string, int, bool> state) pure virtual function in the actual date_drawer observer class, hence we have date* c inside it (not observable*) and for example we can call a non-virtual function of date* (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 use dynamic_cast downcasting which has some runtime overhead.
请注意:
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;
}
输出将是:
Derived1 method
Derived2 method
这里你可以看到一个很好的例子。如果你使用虚方法,程序将知道在运行时执行什么。实现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);
}
};
这不是一个直接的答案,而是CRTP如何发挥作用的一个例子。
一个很好的CRTP的具体例子是std::enable_shared_from_this from c++ 11:
(util.smartptr.enab) / 1 类T可以从enable_-shared_-from_-this <T>继承shared_-from_-this成员函数,该成员函数获取指向*this的shared_-ptr实例。
也就是说,从std::enable_shared_from_this继承,可以在不访问实例的情况下获得一个共享(或弱)指针(例如,从一个你只知道*this的成员函数)。
当你需要给出一个std::shared_ptr但你只能访问*this时,它很有用:
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));
}
};
你不能直接传递这个而不是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);
推荐文章
- 什么是奇怪的重复模板模式(CRTP)?
- 连接两个向量的最佳方法是什么?
- 在c++中,是通过值传递更好,还是通过引用到const传递更好?
- 在STL中deque到底是什么?
- Windows上最好的免费c++分析器是什么?
- 如何自动转换强类型枚举为int?
- 在一个类中使用具有成员函数的泛型std::function对象
- 'for'循环中的后增量和前增量产生相同的输出
- 虚函数和纯虚函数的区别
- c++中的_tmain()和main()有什么区别?
- 内存泄漏是否正常?
- 当启用c++ 11时,std::vector性能回归
- 什么时候使用哪种指针?
- 如何使用c++在一行中声明和定义多个变量?
- 如何在c++中正确使用名称空间?