Qual è il modello di modello curiosamente ricorrente (CRTP)?

Senza fare riferimento a un libro, qualcuno può fornire una buona spiegazione per il CRTP con un esempio di codice?

In breve, CRTP è quando una class A ha una class base che è una specializzazione template per la class A stessa. Per esempio

 template  class X{...}; class A : public X {...}; 

È curiosamente ricorrente, non è vero? 🙂

Ora, cosa ti dà questo? Questo in realtà dà al modello X la possibilità di essere una class base per le sue specializzazioni.

Ad esempio, potresti creare una class generica di singleton (versione semplificata) come questa

 template  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  T* Singleton::p = nullptr; 

Ora, per rendere una class arbitraria A un singleton, dovresti farlo

 class A: public Singleton { //Rest of functionality for class A }; 

Come vedi? Il modello singleton presuppone che la sua specializzazione per qualsiasi tipo X sarà ereditata da singleton e quindi avrà tutti i suoi membri (pubblici, protetti) accessibili, inclusa la GetInstance ! Ci sono altri usi utili di CRTP. Per esempio, se vuoi contare tutte le istanze che esistono attualmente per la tua class, ma vuoi incapsulare questa logica in un modello separato (l’idea per una class concreta è abbastanza semplice – avere una variabile statica, incrementare in ctor, decrementare in dtors ). Prova a farlo come esercizio!

Ancora un altro esempio utile, per boost (non sono sicuro di come l’hanno implementato, ma anche CRTP lo farà). Immagina di voler fornire solo l’operatore

potresti farlo in questo modo:

 template class Equality { }; template  bool operator == (Equality const& op1, Equality const & op2) { Derived const& d1 = static_cast(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(op2); return !(d1 < d2) && !(d2 < d1);//assuming derived has operator < } 

Ora puoi usarlo in questo modo

 struct Apple:public Equality { int size; }; bool operator < (Apple const & a1, Apple const& a2) { return a1.size < a2.size; } 

ora, non hai fornito esplicitamente l'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! { } } 

Potrebbe sembrare che scriverei meno se hai appena scritto l'operatore == per Apple, ma immagina che il modello Equality fornisca non solo == ma>,> =, <= ecc. E potresti usare queste definizioni per più classi, riutilizzare 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. Implementando CRTP il compilatore è quello che decide in fase di compilazione !!! Questa è una grande prestazione!

 template  class Writer { public: Writer() { } ~Writer() { } void write(const char* str) const { static_cast(this)->writeImpl(str); //here the magic is!!! } }; class FileWriter : public Writer { 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 { public: ConsoleWriter() { } ~ConsoleWriter() { } void writeImpl(const char* str) const { printf("%s\n", str); } }; 

Proprio come nota:

CRTP potrebbe essere utilizzato per implementare il polimorfismo statico (che come il polimorfismo dinamico ma senza tabella dei puntatori di funzioni virtuali).

 #pragma once #include  template  class Base { public: void method() { static_cast(this)->method(); } }; class Derived1 : public Base { public: void method() { std::cout << "Derived1 method" << std::endl; } }; class Derived2 : public Base { 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 

CRTP è una tecnica per implementare il polimorfismo in fase di compilazione. Ecco un esempio molto semplice. Nell’esempio seguente, ProcessFoo() funziona con l’interfaccia di Base class e Base::Foo richiama il metodo foo() dell’object derivato, che è ciò che si mira a fare con i metodi virtuali.

http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e

 template  struct Base { void foo() { (static_cast(this))->foo(); } }; struct Derived : public Base { void foo() { cout << "derived foo" << endl; } }; struct AnotherDerived : public Base { void foo() { cout << "AnotherDerived foo" << endl; } }; template void ProcessFoo(Base* b) { b->foo(); } int main() { Derived d1; AnotherDerived d2; ProcessFoo(&d1); ProcessFoo(&d2); return 0; } 

Produzione:

 derived foo AnotherDerived foo 

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 from C ++ 11:

[util.smartptr.enab] / 1

Una class T può ereditare da enable_shared_from_this per ereditare le funzioni shared_from_this che ottengono un’istanza shared_ptr punta a *this .

Ossia, ereditando da std::enable_shared_from_this è ansible ottenere un puntatore condiviso (o debole) sulla tua istanza senza accedervi (ad es. Da una funzione membro in cui si conosce solo *this ).

È utile quando devi dare uno std::shared_ptr ma hai solo accesso a *this :

 struct Node; void process_node(const std::shared_ptr &); struct Node : std::enable_shared_from_this // CRTP { std::weak_ptr parent; std::vector> children; void add_child(std::shared_ptr 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 di shared_from_this() è che interromperà il meccanismo di proprietà:

 struct S { std::shared_ptr get_shared() const { return std::shared_ptr(this); } }; // Both shared_ptr think they're the only owner of S. // This invokes UB (double-free). std::shared_ptr s1 = std::make_shared(); std::shared_ptr s2 = s1->get_shared(); assert(s2.use_count() == 1);