Не ссылаясь на книгу, может ли кто-нибудь объяснить хорошее описание CRTP
примером кода?
Каков любопытно повторяющийся шаблон шаблона (CRTP)?
Ответ 1
Короче говоря, 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
};
Так ты видишь? Шаблон singleton предполагает, что его специализация для любого типа X
будет унаследована от singleton<X>
и, таким образом, будут доступны все его (публичные, защищенные) члены, включая GetInstance
! Есть и другие полезные применения CRTP. Например, если вы хотите подсчитать все экземпляры, которые в данный момент существуют для вашего класса, но хотите инкапсулировать эту логику в отдельный шаблон (идея для конкретного класса довольно проста - иметь статическую переменную, инкремент в ctors, декремент в dtors). Попробуйте сделать это как упражнение!
Еще один полезный пример для 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
Ответ 2
Здесь вы можете увидеть отличный пример. Если вы используете виртуальный метод, программа будет знать, что выполняется во время выполнения. Внедряя 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);
}
};
Ответ 3
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
Ответ 4
Как примечание:
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
Ответ 5
Это не прямой ответ, а пример того, как CRTP может быть полезен.
Хорошим конкретным примером CRTP является std::enable_shared_from_this
из С++ 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);