c++ - ¿Cuál es el patrón de plantilla curiosamente recurrente(CRTP)?
templates c++-faq (5)
Sin referirse a un libro, ¿alguien puede proporcionar una buena explicación para CRTP
con un ejemplo de código?
Aquí puedes ver un gran ejemplo. Si usa el método virtual, el programa sabrá qué ejecutar en tiempo de ejecución. ¡Implementando CRTP el compilador es el que decide en tiempo de compilación! Este es un gran rendimiento!
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 es una técnica para implementar el polimorfismo en tiempo de compilación. Aquí hay un ejemplo muy simple. En el ejemplo siguiente, ProcessFoo()
está trabajando con la interfaz de la clase Base
y Base::Foo
invoca el método foo()
del objeto derivado, que es lo que intenta hacer con los métodos virtuales.
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;
}
Salida:
derived foo
AnotherDerived foo
En resumen, CRTP es cuando una clase A tiene una clase base que es una especialización de plantilla para la clase A en sí misma. P.ej
template <class T>
class X{...};
class A : public X<A> {...};
Es curiosamente recurrente, ¿no? :)
Ahora, ¿qué te da esto? En realidad, esto le da a la plantilla X la capacidad de ser una clase base para sus especializaciones.
Por ejemplo, podría crear una clase singleton genérica (versión simplificada) como esta
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;
Ahora, para hacer que una clase arbitraria A sea singleton, debes hacer esto
class A: public Singleton<A>
{
//Rest of functionality for class A
};
¿Como puedes ver? La plantilla singleton asume que su especialización para cualquier tipo X se heredará de singleton<X>
y, por lo tanto, tendrá acceso a todos sus miembros (públicos, protegidos), incluida la GetInstance
. Hay otros usos útiles de CRTP. Por ejemplo, si desea contar todas las instancias que existen actualmente para su clase, pero desea encapsular esta lógica en una plantilla separada (la idea para una clase concreta es bastante simple: tener una variable estática, incrementar en ctors, decrecer en dtors ) ¡Intenta hacerlo como un ejercicio!
Otro ejemplo útil para impulsar (no estoy seguro de cómo lo han implementado, pero CRTP también lo hará). ¡Imagine que desea proporcionar solo operador <para sus clases pero automáticamente operador == para ellos!
podrías hacerlo así:
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, isnit it?
Derived const& d2 = static_cast<Derived const&>(op2);
return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <
}
Ahora puedes usarlo así
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
ahora, ¿no ha proporcionado explícitamente operador == para apple? ¡Pero lo tienes! Puedes escribir
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won''t complain!
{
}
}
Esto podría parecer que escribirías menos si simplemente escribieras operator == para Apple, pero imagina que la plantilla de Igualdad proporcionaría no solo == sino>,> =, <= etc. Y podrías usar estas definiciones para múltiples clases, reutilizando el código!
CRTP es algo maravilloso :) HTH
Esta no es una respuesta directa, sino más bien un ejemplo de cómo la CRTP puede ser útil.
Un buen ejemplo concreto de CRTP es std::enable_shared_from_this
de C ++ 11:
Una clase
T
puede heredar deenable_shared_from_this<T>
para heredar las funcionesshared_from_this
member que obtienen una instanciashared_ptr
apuntando a*this
.
Es decir, heredar de std::enable_shared_from_this
hace posible obtener un puntero compartido (o débil) para su instancia sin acceso a él (por ejemplo, desde una función miembro donde solo conoce *this
).
Es útil cuando necesitas dar std::shared_ptr
pero solo tienes acceso a *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));
}
};
La razón por la que no puedes pasar this
directamente en lugar de shared_from_this()
es que rompería el mecanismo de propiedad:
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);
Solo como nota:
CRTP podría usarse para implementar polimorfismo estático (que al igual que el polimorfismo dinámico pero sin tabla de puntero de función virtual).
#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;
}
La salida sería:
Derived1 method
Derived2 method