2010-11-13 20 views
131

Senza fare riferimento a un libro, qualcuno può fornire una buona spiegazione per CRTP con un esempio di codice?Qual è il modello di modello curiosamente ricorrente (CRTP)?

+2

domande Leggi CRTP su SO: http://stackoverflow.com/questions/tagged/crtp. Potrebbe darti un'idea. – sbi

+48

@sbi: Se lo fa, troverà la sua domanda. E questo sarebbe curiosamente ricorrente. :) –

+1

BTW, mi sembra che il termine dovrebbe essere "curiosamente ricorsivo". Sto fraintendendo il significato? –

risposta

208

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> {...}; 

E è curiosamente ricorrenti, non è vero? :)

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

Ad esempio, si potrebbe fare una classe Singleton generico (versione semplificata) come questo

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, al fine di rendere una classe arbitraria Un singleton si dovrebbe fare questo

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 sarà ereditata da singleton<X> e quindi avrà tutti i suoi membri (pubblici, protetti) accessibili, incluso lo GetInstance! Ci sono altri usi utili di CRTP. Per esempio, se vuoi contare tutte le istanze che esistono attualmente per la tua classe, ma vuoi incapsulare questa logica in un modello separato (l'idea per una classe 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 CRTP lo farà anche). Immagina di voler fornire solo l'operatore < per le tue classi, ma automaticamente l'operatore == per loro!

si potrebbe fare 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, isnit it? 
    Derived const& d2 = static_cast<Derived const&>(op2); 
    return !(d1 < d2) && !(d2 < d1);//assuming derived has operator < 
} 

Ora è possibile utilizzare 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 è stato fornito in modo esplicito operatore == per Apple? Ma ce l'hai! È possibile 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 hai appena scritto operatore == per Apple, ma immaginare che il modello di uguaglianza fornirebbe non solo == ma>,> =, < = etc. E potresti utilizzare queste definizioni per più classi, riutilizzando il codice!

CRTP è una cosa meravigliosa :) HTH

+0

@Armen Tsirunyan: Questa è una bella risposta! grazie aiuta :) Nell'esempio 2 che hai fornito, la classe basata su modello può essere utilizzata solo come classe base per CRTP, dal presupposto ==. In generale in AWL, le classi di modelli sono implementate e specificatamente contrassegnate per l'utilizzo in CRTP o sono implementate indipendentemente senza alcuna dipendenza/ipotesi come il 2o esempio? –

+0

@Als: No, i contenitori STL non sono progettati con CRTP in mente. Tutti i loro operatori sono forniti in modo indipendente. –

+7

@DeadMG: Onestamente, questa risposta è stata utile per comprendere il concetto di CRTP, anche se singleton non sarebbe la scelta migliore di un esempio, ma la risposta è stata utile allo scopo.Dato che hai downvoted, spero che tu possa trovare una risposta/esempio migliore e tenere la finestra aperta per te non contrassegnando questa come risposta accettata. –

6

Proprio come nota:

CRTP potrebbe essere utilizzato per implementare il polimorfismo statico (che, come il polimorfismo dinamico ma senza tavolo puntatore a funzione virtuale).

#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; 
} 

Il risultato sarebbe:

Derived1 method 
Derived2 method 
+0

Penso che questa operazione si interromperà non appena si utilizza Base con una derivata che è anche tempo di esecuzione polimorfico perché il cast statico del puntatore alla base non determinerebbe il corretto puntatore a derivato. – odinthenerd

+0

@PorkyBrain: non riesco a capire ... potresti dare un esempio del mondo reale? – Jichao

+1

scusate il mio male, static_cast si prende cura del cambiamento. Se si vuole vedere comunque il caso angolo anche se non causa errori, vedere qui: http://ideone.com/LPkktf – odinthenerd

26

Qui potete vedere un grande 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 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); 
    } 
}; 
+0

Non potresti farlo definendo 'virtual void write (const char * str) const = 0;'? Sebbene sia giusto, questa tecnica sembra super utile quando 'write' sta facendo altro lavoro. – atlex2

+7

Utilizzando un metodo virtuale puro si risolve l'ereditarietà in runtime invece del tempo di compilazione. CRTP viene utilizzato per risolvere questo problema in fase di compilazione, quindi l'esecuzione sarà più veloce. – GutiMac

2

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:

[util.smartptr.enab]/1

Una classe può ereditare da Tenable_­shared_­from_­this<T> per ereditare le funzioni shared_­from_­this membro che ottengono un shared_­ptr istanza che punta a *this.

Cioè, eredita da std::enable_shared_from_this permette di ottenere una (o debole) puntatore condiviso per l'istanza, senza accesso ad essa (ad esempio, da una funzione membro in cui si conosce solo il *this).

E 'utile quando è necessario dare un std::shared_ptr ma avete solo l'accesso ai *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 ragione per cui non si può semplicemente passare this direttamente invece di shared_from_this() è che si spezzerebbe 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); 
0

CRTP è una tecnica per implementare il polimorfismo in fase di compilazione. Ecco un esempio molto semplice. Nell'esempio sottostante, ProcessFoo() sta funzionando con l'interfaccia di classe Base e Base::Foo invoca il metodo foo() dell'oggetto derivato, che è ciò che si mira a 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; 
} 

uscita:

derived foo 
AnotherDerived foo 
Problemi correlati