Sin referirse a un libro, ¿alguien puede dar una buena explicación CRTP
con un ejemplo de código?
Sin referirse a un libro, ¿alguien puede dar una buena explicación CRTP
con un ejemplo de código?
Respuestas:
En resumen, CRTP es cuando una clase A
tiene una clase base que es una especialización de plantilla para la clase A
misma. P.ej
template <class T>
class X{...};
class A : public X<A> {...};
Se está curiosamente recurrente, no es así? :)
Ahora, ¿qué te da esto? Esto realmente le da a la X
plantilla la capacidad de ser una clase base para sus especializaciones.
Por ejemplo, podría hacer 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 que una clase arbitraria sea A
un singleton, debes hacer esto
class A: public Singleton<A>
{
//Rest of functionality for class A
};
¿Como puedes ver? La plantilla singleton supone que su especialización para cualquier tipo X
se heredará singleton<X>
y, por lo tanto, tendrá todos sus miembros (públicos, protegidos) accesibles, incluido el 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 de una clase concreta es bastante simple: tenga una variable estática, incremente en ctors, disminuya en dtors ) ¡Intenta hacerlo como ejercicio!
Otro ejemplo útil más para Boost (no estoy seguro de cómo lo han implementado, pero CRTP también lo hará). ¡Imagina que deseas proporcionar solo un operador <
para tus clases pero automáticamente un operador ==
para ellas!
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, isn't 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, usted 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 usted escribiría menos si se acaba de escribir operador ==
para Apple
, pero imagina que la Equality
plantilla proporcionaría no sólo ==
pero >
, >=
, <=
etc, y se podría utilizar estas definiciones para múltiples clases, la reutilización del código!
CRTP es una cosa maravillosa :) HTH
Aquí puedes ver un gran ejemplo. Si usa un 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 !!! Esta es una gran actuación!
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;
? Aunque para ser justos, esta técnica parece muy útil cuando se write
está haciendo otro trabajo.
CRTP es una técnica para implementar el polimorfismo en tiempo de compilación. Aquí hay un ejemplo muy simple. En el siguiente ejemplo, ProcessFoo()
está trabajando con Base
la interfaz de clase e Base::Foo
invoca el foo()
método del objeto derivado , que es lo que pretende 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
foo()
sea implementada por la clase derivada.
ProcessFoo()
función.
void ProcessFoo(T* b)
y sin tener Derived y AnotherDerived realmente derivado, todavía funcionaría. En mi humilde opinión, sería más interesante si ProcessFoo no hiciera uso de plantillas de alguna manera.
ProcessFoo()
funcionará con cualquier tipo que implemente la interfaz, es decir, en este caso, el tipo de entrada T debería tener un método llamado foo()
. En segundo lugar, con el fin de obtener una no plantilla ProcessFoo
para trabajar con múltiples tipos, es probable que termine usando RTTI, que es lo que queremos evitar. Además, la versión con plantilla proporciona una verificación del tiempo de compilación en la interfaz.
Esta no es una respuesta directa, sino un ejemplo de cómo 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 lasshared_from_this
funciones miembro que obtienen unashared_ptr
instancia que apunta*this
.
Es decir, heredar de std::enable_shared_from_this
hace posible obtener un puntero compartido (o débil) a su instancia sin acceso a él (por ejemplo, desde una función miembro de la que solo conoce *this
).
Es útil cuando necesita dar un std::shared_ptr
pero solo tiene 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 puede pasar this
directamente en lugar de shared_from_this()
es porque 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 polimorfismos estáticos (que les gusta el polimorfismo dinámico pero sin la 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;
}
El resultado sería:
Derived1 method
Derived2 method
vtable
s sin usar CRTP. Lo que vtable
realmente proporciona es usar la clase base (puntero o referencia) para llamar a métodos derivados. Debe mostrar cómo se hace con CRTP aquí.
Base<>::method ()
ni siquiera se llama, ni usas polimorfismo en ningún lado.
methodImpl
en el method
de Base
y en las clases derivadas nombrar methodImpl
en lugar demethod