@sbi: Se ele fizer isso, ele encontrará sua própria pergunta. E isso seria curiosamente recorrente. :)
Craig McQueen
1
BTW, parece-me que o termo deve ser "curiosamente recorrente". Estou entendendo mal o significado?
Craig McQueen
1
Craig: Eu acho que você é; é "curiosamente recorrente" no sentido de que foi encontrado em vários contextos.
precisa saber é o seguinte
Respostas:
276
Em resumo, o CRTP é quando uma classe Atem uma classe base, que é uma especialização de modelo para a Aprópria classe . Por exemplo
template<class T>class X{...};class A :public X<A>{...};
Ele é curiosamente recorrente, não é? :)
Agora, o que isso lhe dá? Isso realmente dá ao Xmodelo a capacidade de ser uma classe base para suas especializações.
Por exemplo, você pode criar uma classe singleton genérica (versão simplificada) como esta
template<classActualClass>classSingleton{public:staticActualClass&GetInstance(){if(p ==nullptr)
p =newActualClass;return*p;}protected:staticActualClass* p;private:Singleton(){}Singleton(Singletonconst&);Singleton&operator=(Singletonconst&);};template<class T>
T*Singleton<T>::p =nullptr;
Agora, para tornar uma classe arbitrária Aum singleton, você deve fazer isso
class A:publicSingleton<A>{//Rest of functionality for class A};
Então você vê? O modelo singleton assume que sua especialização para qualquer tipo Xserá herdada singleton<X>e, portanto, terá todos os seus membros (públicos, protegidos) acessíveis, incluindo o GetInstance! Existem outros usos úteis do CRTP. Por exemplo, se você deseja contar todas as instâncias que existem atualmente para sua classe, mas deseja encapsular essa lógica em um modelo separado (a idéia para uma classe concreta é bastante simples - tenha uma variável estática, incremento em ctors, decremento em dtors ) Tente fazer isso como um exercício!
Outro exemplo útil, para o Boost (não sei como eles o implementaram, mas o CRTP também o fará). Imagine que você deseja fornecer apenas um operador <para suas classes, mas automaticamente um operador ==para elas!
você poderia fazer assim:
template<classDerived>classEquality{};template<classDerived>booloperator==(Equality<Derived>const& op1,Equality<Derived>const& op2){Derivedconst& d1 =static_cast<Derivedconst&>(op1);//you assume this works //because you know that the dynamic type will actually be your template parameter.//wonderful, isn't it?Derivedconst& d2 =static_cast<Derivedconst&>(op2);return!(d1 < d2)&&!(d2 < d1);//assuming derived has operator <}
Pode parecer que você escreveria menos se acabasse de escrever ==para o operador Apple, mas imagine que o Equalitymodelo forneceria não apenas , ==mas , etc. E você poderia usar essas definições para várias classes, reutilizando o código!>>=<=
Este post não defende singleton como um pattern.it boa programar simplesmente usa-lo como uma ilustração que pode ser comumente understood.imo the-1 é injustificada
John Dibling
3
@ Armmen: A resposta explica o CRTP de uma maneira que possa ser entendida claramente, é uma boa resposta, obrigado por uma resposta tão boa.
Alok Salvar
1
@ Armmen: obrigado por esta ótima explicação. Eu estava meio que recebendo CRTP antes, mas o exemplo da igualdade tem sido esclarecedor! +1
Paulo
1
Outro exemplo de uso de CRTP é quando você precisa de uma classe não copiável: modelo <classe T> classe NonCopyable {protected: NonCopyable () {} ~ NonCopyable () {} private: NonCopyable (const NonCopyable &); NonCopyable & operator = (const Não copiável &); }; Então você usa noncopyable como abaixo: class Mutex: private NonCopyable <Mutex> {public: void Lock () {} void UnLock () {}};
Virén
2
@ Filhote: Singleton não é terrível. É muito usado em excesso por programadores abaixo da média quando outras abordagens seriam mais apropriadas, mas o fato de a maioria de seus usos ser terrível não torna o padrão em si terrível. Há casos em que o singleton é a melhor opção, embora esses sejam raros.
Kaiserludi
47
Aqui você pode ver um ótimo exemplo. Se você usar o método virtual, o programa saberá o que é executado em tempo de execução. Implementando o CRTP, o compilador é o que decide em tempo de compilação !!! Este é um ótimo desempenho!
template<class T>classWriter{public:Writer(){}~Writer(){}void write(constchar* str)const{static_cast<const T*>(this)->writeImpl(str);//here the magic is!!!}};classFileWriter:publicWriter<FileWriter>{public:FileWriter(FILE* aFile){ mFile = aFile;}~FileWriter(){ fclose(mFile);}//here comes the implementation of the write method on the subclassvoid writeImpl(constchar* str)const{
fprintf(mFile,"%s\n", str);}private:FILE* mFile;};classConsoleWriter:publicWriter<ConsoleWriter>{public:ConsoleWriter(){}~ConsoleWriter(){}void writeImpl(constchar* str)const{
printf("%s\n", str);}};
Você não poderia fazer isso definindo virtual void write(const char* str) const = 0;? Embora seja justo, essa técnica parece super útil quando se writeestá fazendo outro trabalho.
Atlet2
26
Usando um método virtual puro, você está resolvendo a herança em tempo de execução, em vez de tempo de compilação. O CRTP é usado para resolver isso em tempo de compilação, para que a execução seja mais rápida.
GutiMac
1
Tente criar uma função simples que espere um Writer abstrato: você não pode fazê-lo porque não há classe chamada Writer em lugar algum; então, onde está exatamente o seu polimorfismo? Isso não é equivalente a funções virtuais e é muito menos útil.
22
O CRTP é uma técnica para implementar o polimorfismo em tempo de compilação. Aqui está um exemplo muito simples. No exemplo abaixo, ProcessFoo()está trabalhando com a Baseinterface de classe e Base::Foochama o foo()método do objeto derivado , que é o que você pretende fazer com os métodos virtuais.
Também pode valer a pena neste exemplo adicionar um exemplo de como implementar um foo () padrão na classe Base que será chamado se nenhum Derivado o implementou. AKA mude foo na Base para outro nome (por exemplo, chamador ()), adicione uma nova função foo () à Base que cout a "Base". Então chamador chamada () dentro de ProcessFoo
wizurd
@wizurd Este exemplo é mais para ilustrar uma função pura da classe base virtual, ou seja, aplicamos o que foo()é implementado pela classe derivada.
Blueskin
3
Esta é a minha resposta favorita, pois também mostra por que esse padrão é útil com a ProcessFoo()função.
Pietro
Eu não entendi o ponto desse código, porque com void ProcessFoo(T* b)e sem a derivação de Derived e AnotherDerived, ele ainda funcionaria. IMHO seria mais interessante se o ProcessFoo não fizesse uso de modelos de alguma forma.
Gabriel Devillers 04/06
1
@GabrielDevillers Em primeiro lugar, o modelo ProcessFoo()funcionará com qualquer tipo que implemente a interface, ou seja, nesse caso, o tipo de entrada T deve ter um método chamado foo(). Segundo, para que um modelo não-modelo ProcessFoofuncione com vários tipos, você provavelmente acabará usando o RTTI, que é o que queremos evitar. Além disso, a versão padronizada fornece a verificação do tempo de compilação na interface.
blueskin 12/06
6
Esta não é uma resposta direta, mas um exemplo de como o CRTP pode ser útil.
Um bom exemplo concreto de CRTP é std::enable_shared_from_thisdo C ++ 11:
Uma classe Tpode herdar de enable_shared_from_this<T>para herdar as shared_from_thisfunções de membro que obtêm uma shared_ptrinstância apontando *this.
Ou seja, herdar de std::enable_shared_from_thistorna possível obter um ponteiro compartilhado (ou fraco) para sua instância sem acesso a ela (por exemplo, de uma função membro na qual você conhece apenas *this).
É útil quando você precisa dar um, std::shared_ptrmas você só tem acesso a *this:
O motivo pelo qual você não pode simplesmente passar thisdiretamente em vez de shared_from_this()é que isso quebraria o mecanismo de propriedade:
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);
desculpe meu mau, static_cast cuida da mudança. Se você quiser ver o caso canto de qualquer maneira, mesmo que ele não causa erro ver aqui: ideone.com/LPkktf
odinthenerd
30
Mau exemplo. Este código pode ser feito sem no vtablesem o uso de CRTP. o quevtable realmente é fornecido é usar a classe base (ponteiro ou referência) para chamar métodos derivados. Você deve mostrar como isso é feito com o CRTP aqui.
Etherealone
17
No seu exemplo, Base<>::method () nem sequer é chamado, nem você usa polimorfismo em lugar algum.
MikeMB
1
@Jichao, de acordo com a nota de @MikeMB, você deve chamar methodImplo nome methodde Basee nas classes derivadasmethodImpl vez demethod
Ivan Kush
1
se você usar o método semelhante (), ele será vinculado estaticamente e você não precisará da classe base comum. De qualquer maneira, você não pode usá-lo polimorficamente através do ponteiro da classe base ou ref. Portanto, o código deve ficar assim: #include <iostream> modelo <nome do tipo T> struct Writer {void write () {static_cast <T *> (this) -> writeImpl (); }}; struct Derivado1: gravador público <Derivado1> {void writeImpl () {std :: cout << "D1"; }}; struct Derived2: public Writer <Derived2> {void writeImpl () {std :: cout << "DER2"; }};
Respostas:
Em resumo, o CRTP é quando uma classe
A
tem uma classe base, que é uma especialização de modelo para aA
própria classe . Por exemploEle é curiosamente recorrente, não é? :)
Agora, o que isso lhe dá? Isso realmente dá ao
X
modelo a capacidade de ser uma classe base para suas especializações.Por exemplo, você pode criar uma classe singleton genérica (versão simplificada) como esta
Agora, para tornar uma classe arbitrária
A
um singleton, você deve fazer issoEntão você vê? O modelo singleton assume que sua especialização para qualquer tipo
X
será herdadasingleton<X>
e, portanto, terá todos os seus membros (públicos, protegidos) acessíveis, incluindo oGetInstance
! Existem outros usos úteis do CRTP. Por exemplo, se você deseja contar todas as instâncias que existem atualmente para sua classe, mas deseja encapsular essa lógica em um modelo separado (a idéia para uma classe concreta é bastante simples - tenha uma variável estática, incremento em ctors, decremento em dtors ) Tente fazer isso como um exercício!Outro exemplo útil, para o Boost (não sei como eles o implementaram, mas o CRTP também o fará). Imagine que você deseja fornecer apenas um operador
<
para suas classes, mas automaticamente um operador==
para elas!você poderia fazer assim:
Agora você pode usá-lo assim
Agora, você não forneceu explicitamente operador
==
paraApple
? Mas você tem! Você pode escreverPode parecer que você escreveria menos se acabasse de escrever
==
para o operadorApple
, mas imagine que oEquality
modelo forneceria não apenas ,==
mas , etc. E você poderia usar essas definições para várias classes, reutilizando o código!>
>=
<=
CRTP é uma coisa maravilhosa :) HTH
fonte
Aqui você pode ver um ótimo exemplo. Se você usar o método virtual, o programa saberá o que é executado em tempo de execução. Implementando o CRTP, o compilador é o que decide em tempo de compilação !!! Este é um ótimo desempenho!
fonte
virtual void write(const char* str) const = 0;
? Embora seja justo, essa técnica parece super útil quando sewrite
está fazendo outro trabalho.O CRTP é uma técnica para implementar o polimorfismo em tempo de compilação. Aqui está um exemplo muito simples. No exemplo abaixo,
ProcessFoo()
está trabalhando com aBase
interface de classe eBase::Foo
chama ofoo()
método do objeto derivado , que é o que você pretende fazer com os métodos virtuais.http://coliru.stacked-crooked.com/a/2d27f1e09d567d0e
Resultado:
fonte
foo()
é implementado pela classe derivada.ProcessFoo()
função.void ProcessFoo(T* b)
e sem a derivação de Derived e AnotherDerived, ele ainda funcionaria. IMHO seria mais interessante se o ProcessFoo não fizesse uso de modelos de alguma forma.ProcessFoo()
funcionará com qualquer tipo que implemente a interface, ou seja, nesse caso, o tipo de entrada T deve ter um método chamadofoo()
. Segundo, para que um modelo não-modeloProcessFoo
funcione com vários tipos, você provavelmente acabará usando o RTTI, que é o que queremos evitar. Além disso, a versão padronizada fornece a verificação do tempo de compilação na interface.Esta não é uma resposta direta, mas um exemplo de como o CRTP pode ser útil.
Um bom exemplo concreto de CRTP é
std::enable_shared_from_this
do C ++ 11:Ou seja, herdar de
std::enable_shared_from_this
torna possível obter um ponteiro compartilhado (ou fraco) para sua instância sem acesso a ela (por exemplo, de uma função membro na qual você conhece apenas*this
).É útil quando você precisa dar um,
std::shared_ptr
mas você só tem acesso a*this
:O motivo pelo qual você não pode simplesmente passar
this
diretamente em vez deshared_from_this()
é que isso quebraria o mecanismo de propriedade:fonte
Apenas como nota:
O CRTP pode ser usado para implementar polimorfismo estático (que gosta de polimorfismo dinâmico, mas sem tabela de ponteiros de função virtual).
A saída seria:
fonte
vtable
sem o uso de CRTP. o quevtable
realmente é fornecido é usar a classe base (ponteiro ou referência) para chamar métodos derivados. Você deve mostrar como isso é feito com o CRTP aqui.Base<>::method ()
nem sequer é chamado, nem você usa polimorfismo em lugar algum.methodImpl
o nomemethod
deBase
e nas classes derivadasmethodImpl
vez demethod