不参考书籍,任何人都可以CRTP
通过代码示例提供很好的解释吗?
不参考书籍,任何人都可以CRTP
通过代码示例提供很好的解释吗?
Answers:
简而言之,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
};
所以你看?单例模板假定其任何类型的专业化都X
将从其继承singleton<X>
,从而可以访问其所有(公共,受保护)成员,包括GetInstance
!CRTP还有其他有用的用途。例如,如果您要计算您的类当前存在的所有实例,但想将此逻辑封装在单独的模板中(具体类的想法很简单-拥有一个静态变量,ctor中的增量,dtor中的递减)。尝试做为练习!
对于Boost,还有另一个有用的示例(我不确定他们是如何实现的,但是CRTP也会这样做)。想象一下,您只想<
为您的班级提供操作员,而==
为他们提供自动操作员!
您可以这样做:
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
?但是你有!你可以写
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
这可能似乎是你会少写,如果你只是写操作==
的Apple
,但想象Equality
模板将不仅提供==
,但是>
,>=
,<=
等你可以使用这些定义为多个类,重用代码!
CRTP是一件了不起的事情:) HTH
在这里您可以看到一个很好的例子。如果使用虚拟方法,程序将知道在运行时执行什么。实现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);
}
};
virtual void write(const char* str) const = 0;
吗?公平地说,这项技术write
在进行其他工作时似乎很有帮助。
CRTP是一种实现编译时多态性的技术。这是一个非常简单的示例。在下面的示例中,ProcessFoo()
正在使用Base
类接口并Base::Foo
调用派生对象的foo()
方法,这是您要使用虚拟方法实现的目标。
http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e
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;
}
输出:
derived foo
AnotherDerived foo
foo()
执行由派生类实现的函数。
ProcessFoo()
函数有用的原因。
void ProcessFoo(T* b)
无论是否有Derived和AnotherDerived实际派生,它仍然可以工作。恕我直言,如果ProcessFoo不以某种方式使用模板,将会更有趣。
ProcessFoo()
将与实现接口的任何类型一起使用,即在这种情况下,输入类型T应该具有称为的方法foo()
。其次,为了使非模板化ProcessFoo
可以使用多种类型,您可能最终会使用RTTI,这是我们想要避免的。此外,模板化版本为您提供了在界面上检查编译时间的功能。
这不是直接的答案,而是CRTP如何有用的一个示例。
的一个很好的具体的例子CRTP是std::enable_shared_from_this
从C ++ 11:
类
T
可以继承自来enable_shared_from_this<T>
继承shared_from_this
获得shared_ptr
指向的实例的成员函数*this
。
也就是说,继承自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));
}
};
您不能直接通过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);
请注意:
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
vtable
不使用CRTP,则可以使用no来完成此代码。什么vtable
š真正提供使用基类(指针或引用)来调用导出方法是。您应该在此处显示如何使用CRTP进行操作。
Base<>::method ()
甚至没有调用,也不在任何地方使用多态。
methodImpl
中method
的Base
和在派生类的名字methodImpl
,而不是method