Sem se referir a um livro, alguém pode fornecer uma boa explicação para CRTP
um exemplo de código?
Sem se referir a um livro, alguém pode fornecer uma boa explicação para CRTP
um exemplo de código?
Respostas:
Em resumo, o CRTP é quando uma classe A
tem uma classe base, que é uma especialização de modelo para a A
pró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 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
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 A
um 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 X
será 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 Equality
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
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 write
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 a Base
interface de classe e Base::Foo
chama 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 ProcessFoo
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:
Uma classe
T
pode herdar deenable_shared_from_this<T>
para herdar asshared_from_this
funções de membro que obtêm umashared_ptr
instância apontando*this
.
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
:
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 this
diretamente 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
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 nome method
de Base
e nas classes derivadasmethodImpl
vez demethod