Senza fare riferimento a un libro, qualcuno può fornire una buona spiegazione CRTP
con un esempio di codice?
Senza fare riferimento a un libro, qualcuno può fornire una buona spiegazione CRTP
con un esempio di codice?
Risposte:
In breve, CRTP è quando una classe A
ha una classe base che è una specializzazione di modello per la classe A
stessa. Per esempio
template <class T>
class X{...};
class A : public X<A> {...};
Lo è curiosamente ripresentando, non è vero? :)
Ora, cosa ti dà questo? Questo in realtà dà ilX
modello la possibilità di essere una classe base per le sue specializzazioni.
Ad esempio, potresti creare una classe singleton generica (versione semplificata) come questa
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;
Ora, per rendere una classe arbitraria A
un singleton dovresti farlo
class A: public Singleton<A>
{
//Rest of functionality for class A
};
Come vedi? Il modello singleton presuppone che la sua specializzazione per qualsiasi tipo X
verrà ereditata singleton<X>
e quindi tutti i suoi membri (pubblici, protetti) saranno accessibili, incluso il GetInstance
! Esistono altri usi utili di CRTP. Ad esempio, se si desidera contare tutte le istanze attualmente esistenti per la propria classe, ma si desidera incapsulare questa logica in un modello separato (l'idea di una classe concreta è abbastanza semplice: disporre di una variabile statica, incremento in dottori, decremento in dottori ). Prova a farlo come esercizio!
Ancora un altro esempio utile, per Boost (non sono sicuro di come lo abbiano implementato, ma lo farà anche CRTP). Immagina di voler fornire solo un operatore <
per le tue classi, ma automaticamente un operatore ==
per loro!
potresti farlo in questo modo:
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 <
}
Ora puoi usarlo in questo modo
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
Ora, non hai fornito esplicitamente operatore ==
per Apple
? Ma ce l'hai! Tu puoi scrivere
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
Questo potrebbe sembrare che si può scrivere di meno se appena scritto operatore ==
per Apple
, ma immaginare che il Equality
modello avrebbe fornito non solo ==
, ma >
, >=
, <=
ecc E si potrebbe usare queste definizioni per più classi, riutilizzando il codice!
CRTP è una cosa meravigliosa :) HTH
Qui puoi vedere un ottimo esempio. Se si utilizza il metodo virtuale, il programma saprà cosa eseguire in fase di esecuzione. L'implementazione di CRTP è il compilatore che decide in tempo di compilazione !!! Questa è una grande esibizione!
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;
? Sebbene sia corretta, questa tecnica sembra molto utile quando write
si svolgono altri lavori.
CRTP è una tecnica per implementare il polimorfismo in fase di compilazione. Ecco un esempio molto semplice. Nell'esempio seguente, ProcessFoo()
lavora con l' Base
interfaccia di classe e Base::Foo
richiama il foo()
metodo dell'oggetto derivato , che è ciò che intendi fare con i metodi virtuali.
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;
}
Produzione:
derived foo
AnotherDerived foo
foo()
è implementato dalla classe derivata.
ProcessFoo()
funzione.
void ProcessFoo(T* b)
e senza avere Derived e AnotherDerived effettivamente funzionato funzionerebbe comunque. IMHO sarebbe più interessante se ProcessFoo non usasse i template in qualche modo.
ProcessFoo()
funzionerà con qualsiasi tipo che implementa l'interfaccia, ovvero in questo caso il tipo di input T dovrebbe avere un metodo chiamato foo()
. In secondo luogo, al fine di ottenere un non templatized ProcessFoo
per lavorare con più tipi, probabilmente finiresti per usare RTTI, che è ciò che vogliamo evitare. Inoltre, la versione templatizzata fornisce il controllo del tempo di compilazione sull'interfaccia.
Questa non è una risposta diretta, ma piuttosto un esempio di come CRTP può essere utile.
Un buon esempio concreto di CRTP è std::enable_shared_from_this
da C ++ 11:
Una classe
T
può ereditare daenable_shared_from_this<T>
per ereditare leshared_from_this
funzioni membro che ottengonoshared_ptr
un'istanza che punta a*this
.
Cioè, ereditare da std::enable_shared_from_this
consente di ottenere un puntatore condiviso (o debole) alla propria istanza senza accedervi (ad es. Da una funzione membro di cui si conosce solo *this
).
È utile quando devi dare un std::shared_ptr
ma hai accesso solo 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));
}
};
Il motivo per cui non puoi semplicemente passare this
direttamente invece shared_from_this()
è che si romperà il meccanismo di proprietà:
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);
Proprio come nota:
Il CRTP potrebbe essere usato per implementare il polimorfismo statico (che come il polimorfismo dinamico ma senza una tabella di puntatori di funzioni virtuali).
#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;
}
L'output sarebbe:
Derived1 method
Derived2 method
vtable
senza utilizzare CRTP. Ciò vtable
che realmente fornisce è l'uso della classe base (puntatore o riferimento) per chiamare metodi derivati. Qui dovresti mostrare come si fa con CRTP.
Base<>::method ()
non viene nemmeno chiamato, né usi il polimorfismo da nessuna parte.
methodImpl
il nome method
di Base
e in classi derivate methodImpl
invece dimethod