Sans faire référence à un livre, quelqu'un peut-il fournir une bonne explication CRTP
avec un exemple de code?
Sans faire référence à un livre, quelqu'un peut-il fournir une bonne explication CRTP
avec un exemple de code?
Réponses:
En bref, CRTP se produit lorsqu'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> {...};
Il est récurrent curieusement, est - ce pas? :)
Maintenant, qu'est-ce que cela vous donne? Cela donne en fait au X
modèle 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, pour faire d'une classe arbitraire A
un singleton, vous devez faire ceci
class A: public Singleton<A>
{
//Rest of functionality for class A
};
Donc tu vois? Le modèle singleton suppose que sa spécialisation pour n'importe quel type X
sera héritée de singleton<X>
et aura donc tous ses membres (publics, protégés) accessibles, y compris le GetInstance
! Il existe d'autres utilisations utiles du CRTP. Par exemple, si vous voulez compter toutes les instances qui existent actuellement pour votre classe, mais que vous voulez 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 en ctors, décrémenter en dtors ). Essayez de le faire comme un exercice!
Encore un autre exemple utile, pour Boost (je ne sais pas comment ils l'ont implémenté, mais CRTP le fera aussi). Imaginez que vous souhaitiez fournir uniquement un opérateur <
pour vos classes mais automatiquement un opérateur ==
pour elles!
vous pouvez 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 tu peux 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 fourni explicitement d'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 imaginez que le Equality
modèle fournirait non seulement ==
mais >
, >=
, <=
etc. Et vous pouvez utiliser ces définitions pour plusieurs cours, la réutilisation du code!
CRTP est une chose merveilleuse :) HTH
Ici vous pouvez voir un excellent exemple. Si vous utilisez la méthode virtuelle, le programme saura ce qui s'exécute à l'exécution. Implémenter CRTP c'est le compilateur qui décide au moment de la 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);
}
};
virtual void write(const char* str) const = 0;
? Bien que pour être juste, cette technique semble très utile lorsque vous write
effectuez un autre travail.
CRTP est une technique pour implémenter le polymorphisme à la compilation. Voici un exemple très simple. Dans l'exemple ci-dessous, ProcessFoo()
travaille avec l' Base
interface de classe et Base::Foo
appelle la foo()
méthode de l'objet dérivé , ce que vous souhaitez faire avec les 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;
}
Production:
derived foo
AnotherDerived foo
foo()
est implémenté par la classe dérivée.
ProcessFoo()
fonction.
void ProcessFoo(T* b)
et sans avoir dérivé et dérivé d'un autre dérivé, il fonctionnerait toujours. À mon humble avis, il serait plus intéressant que ProcessFoo n'utilise pas de modèles d'une manière ou d'une autre.
ProcessFoo()
fonctionnera avec n'importe quel type qui implémente l'interface, c'est-à-dire que dans ce cas, le type d'entrée T devrait avoir une méthode appelée foo()
. Deuxièmement, pour qu'un non-modèle ProcessFoo
fonctionne avec plusieurs types, vous finirez probablement par utiliser RTTI, ce que nous voulons éviter. De plus, la version modélisée vous permet de vérifier le temps de compilation sur l'interface.
Ce n'est pas une réponse directe, mais plutôt un exemple de la façon dont le CRTP peut être utile.
Un bon exemple concret de CRTP provient std::enable_shared_from_this
de C ++ 11:
Une classe
T
peut hériter deenable_shared_from_this<T>
pour hériter desshared_from_this
fonctions membres qui obtiennent uneshared_ptr
instance 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 dont vous ne connaissez que l'existence *this
).
C'est utile lorsque vous devez donner un std::shared_ptr
mais que vous n'avez accès qu'à *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 au lieu 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);
Juste comme note:
CRTP pourrait être utilisé pour implémenter le polymorphisme statique (qui aime le 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;
}
Le résultat serait:
Derived1 method
Derived2 method
vtable
s sans utiliser CRTP. Ce qui vtable
est vraiment fourni, c'est d'utiliser la classe de base (pointeur ou référence) pour appeler des méthodes dérivées. Vous devriez montrer comment cela se fait avec CRTP ici.
Base<>::method ()
n'est même pas appelé, et vous n'utilisez le polymorphisme nulle part.
methodImpl
le nom method
de Base
et dans les classes dérivées methodImpl
au lieu demethod