Sans faire référence à un livre, quelqu'un peut-il fournir une bonne explication de CRTP
avec un exemple de code?
En bref, CRTP est le cas où une classe A
a une classe de base, qui est une spécialisation de modèle pour la classe A
elle-même. Par exemple.
template <class T>
class X{...};
class A : public X<A> {...};
C'est est curieusement récurrent, n'est-ce pas? :)
Maintenant, qu'est-ce que cela vous donne? Cela donne en fait au modèle X
la possibilité d’être une classe de base pour ses spécialisations.
Par exemple, vous pouvez créer une classe singleton générique (version simplifiée) comme celle-ci
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;
Maintenant, afin de faire une classe arbitraire A
un singleton, vous devriez le faire
class A: public Singleton<A>
{
//Rest of functionality for class A
};
Donc tu vois? Le modèle singleton suppose que sa spécialisation pour tout type X
sera héritée de singleton<X>
et tous ses membres (publics, protégés) seront accessibles, y compris le GetInstance
! Il existe d'autres utilisations utiles du CRTP. Par exemple, si vous souhaitez compter toutes les instances existantes dans votre classe, mais que vous souhaitez encapsuler cette logique dans un modèle séparé (l'idée d'une classe concrète est assez simple: avoir une variable statique, incrémenter dans ctors, décrémenter dans dtors ). Essayez de le faire comme un exercice!
Encore un autre exemple utile, pour Boost (je ne sais pas comment ils l’ont mis en œuvre, mais le CRTP le fera aussi). Imaginez que vous souhaitiez fournir uniquement l'opérateur <
pour vos cours mais automatiquement opérateur ==
pour eux!
vous pourriez le faire comme ceci:
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 <
}
Maintenant, vous pouvez l'utiliser comme ça
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
Maintenant, vous n'avez pas explicitement fourni l'opérateur ==
pour Apple
? Mais vous l'avez! Tu peux écrire
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
Cela pourrait sembler que vous écririez moins si vous venez d'écrire l'opérateur ==
pour Apple
, mais imaginons que le modèle Equality
fournisse non seulement ==
mais >
, >=
, <=
etc. Et vous pourriez utiliser ces définitions pour plusieurs classes, en réutilisant le code!
Le CRTP est une chose merveilleuse :) HTH
Ici vous pouvez voir un bon exemple. Si vous utilisez une méthode virtuelle, le programme saura ce qui sera exécuté au moment de l'exécution. L'implémentation de CRTP est le compilateur qui décide en compilation !!! C'est une belle performance!
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);
}
};
Le CRTP est une technique permettant de mettre en œuvre un polymorphisme au moment de la compilation. Voici un exemple très simple. Dans l'exemple ci-dessous, ProcessFoo()
travaille avec Base
interface de classe et Base::Foo
Invoque la méthode foo()
de l'objet dérivé, ce que vous souhaitez faire. avec des méthodes virtuelles.
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;
}
Sortie:
derived foo
AnotherDerived foo
Juste comme note:
CRTP pourrait être utilisé pour implémenter un polymorphisme statique (qui ressemble au polymorphisme dynamique mais sans table de pointeur de fonction virtuelle).
#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;
}
La sortie serait:
Derived1 method
Derived2 method
Ce n'est pas une réponse directe, mais plutôt un exemple de la façon dont [~ # ~] crtp [~ # ~] peut être utile.
Un bon exemple concret de [~ # ~] crtp [~ # ~] est std::enable_shared_from_this
De C++ 11:
Une classe
T
peut hériter deenable_shared_from_this<T>
Pour hériter des fonctions membresshared_from_this
Qui obtiennent une instanceshared_ptr
Pointant vers*this
.
Autrement dit, hériter de std::enable_shared_from_this
Permet d’obtenir un pointeur partagé (ou faible) vers votre instance sans y avoir accès (par exemple, à partir d’une fonction membre où vous ne connaissez que *this
).
C'est utile lorsque vous devez donner un std::shared_ptr
Mais vous avez seulement accès à *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 raison pour laquelle vous ne pouvez pas simplement passer this
directement à la place de shared_from_this()
est que cela briserait le mécanisme de propriété:
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);