Sem se referir a um livro, alguém pode fornecer uma boa explicação para CRTPum exemplo de código?
Sem se referir a um livro, alguém pode fornecer uma boa explicação para CRTPum exemplo de código?
Respostas:
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 <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;
Agora, para tornar uma classe arbitrária Aum singleton, você deve fazer isso
class A: public Singleton<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<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 <
}
Agora você pode usá-lo assim
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
Agora, você não forneceu explicitamente operador ==para Apple? Mas você tem! Você pode escrever
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
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!>>=<=
CRTP é uma coisa maravilhosa :) HTH
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>
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;? Embora seja justo, essa técnica parece super útil quando se writeestá 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 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.
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;
}
Resultado:
derived foo
AnotherDerived foo
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 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.
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 deenable_shared_from_this<T>para herdar asshared_from_thisfunções de membro que obtêm umashared_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:
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));
}
};
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);
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).
#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;
}
A saída seria:
Derived1 method
Derived2 method
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.
Base<>::method () nem sequer é chamado, nem você usa polimorfismo em lugar algum.
methodImplo nome methodde Basee nas classes derivadasmethodImpl vez demethod