Ohne Bezug auf ein Buch, kann jemand bitte eine gute Erklärung für CRTP
mit einem Codebeispiel liefern?Was ist das seltsam wiederkehrende Vorlagenmuster (CRTP)?
Antwort
Kurz gesagt, CRTP ist, wenn eine Klasse A eine Basisklasse hat, die eine Template-Spezialisierung für die Klasse A selbst ist. Z.B.
template <class T>
class X{...};
class A : public X<A> {...};
Es ist neugierig wiederkehrend, es ist nicht wahr? :)
Nun, was gibt dir das? Dies gibt der X-Vorlage die Möglichkeit, eine Basisklasse für ihre Spezialisierungen zu sein.
Zum Beispiel könnten Sie eine generische Singletonklasse (vereinfachte Version) machen, wie diese
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;
Um nun ein Singleton eine beliebige Klasse A zu machen, sollten Sie dies tun
class A: public Singleton<A>
{
//Rest of functionality for class A
};
Nun siehst du? Die Singleton-Vorlage geht davon aus, dass ihre Spezialisierung für jeden Typ X von singleton<X>
übernommen wird und somit alle (öffentlichen, geschützten) Mitglieder zugänglich sind, einschließlich der GetInstance
! Es gibt andere nützliche Anwendungen von CRTP. Wenn Sie beispielsweise alle Instanzen zählen möchten, die derzeit für Ihre Klasse existieren, diese Logik jedoch in einer separaten Vorlage kapseln möchten (die Idee für eine konkrete Klasse ist ziemlich einfach), haben Sie eine statische Variable, inctors inkrementieren, in dtors dekrementieren). Versuchen Sie es als Übung!
Noch ein nützliches Beispiel für Boost (ich bin mir nicht sicher, wie sie es implementiert haben, aber CRTP wird auch tun). Stellen Sie sich vor, Sie möchten nur den Operator < für Ihre Klassen bereitstellen, aber automatisch den Operator == für sie!
Sie es wie folgt tun könnte:
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, isnit it?
Derived const& d2 = static_cast<Derived const&>(op2);
return !(d1 < d2) && !(d2 < d1);//assuming derived has operator <
}
Jetzt können Sie es wie folgt nun
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
verwenden, haben Sie nicht explizit Betreiber zur Verfügung gestellt == für Apfel? Aber du hast es! Sie können schreiben,
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
Dies könnte scheinen, dass Sie weniger schreiben würden, wenn man nur Operator schreibt == für Apple, aber vorstellen, dass die Gleichstellung der Vorlage nicht nur würde == aber>,> =, < = usw. Und Sie könnten diese Definitionen für mehrere Klassen verwenden, den Code wiederverwenden!
CRTP ist eine wunderbare Sache :) HTH
@Armen Tsirunyan: Das ist eine nette Antwort! Danke, dass es hilft :) Im zweiten Beispiel, das Sie angegeben haben, kann die Template-Klasse nur als Basisklasse für CRTP verwendet werden, da die == Annahme. Im Allgemeinen werden in STL Template-Klassen implementiert und speziell für die Verwendung in CRTP markiert oder unabhängig voneinander implementiert, ohne irgendeine Abhängigkeit/Annahme wie im zweiten Beispiel? –
@Als: Nein, STL-Container sind nicht für CRTP konzipiert. Alle ihre Betreiber werden unabhängig bereitgestellt. –
@DeadMG: Ehrlich gesagt, diese Antwort war hilfreich, um das Konzept CRTP zu verstehen, obwohl Singleton nicht die beste Wahl eines Beispiels sein würde, noch die Antwort diente dem Zweck.Da Sie abgelehnt haben, hoffe ich, dass Sie eine bessere Antwort/ein besseres Beispiel finden und das Fenster für Sie offen halten, indem Sie dies nicht als akzeptierte Antwort markieren. –
Wie Anmerkung:
CRTP verwendet werden könnte statischen Polymorphismus (die wie dynamische Polymorphie aber ohne virtuelle Funktionszeigertabelle) zu implementieren.
#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;
}
würde der Ausgang sein:
Derived1 method
Derived2 method
Ich denke, dies würde zusammenbrechen, sobald Sie Base mit einem abgeleiteten verwenden, das auch polymorphe Laufzeit ist, weil der statische Cast des Zeigers auf Basis nicht in einem richtigen Zeiger zu abgeleiteten Ergebnissen führen würde. – odinthenerd
@PorkyBrain: Ich kann es nicht verstehen .. könnten Sie ein Beispiel aus der Praxis geben? – Jichao
Entschuldigung mein schlechter, static_cast kümmert sich um die Änderung. Wenn Sie den Corner Case trotzdem sehen wollen, obwohl er keinen Fehler verursacht, lesen Sie hier: http://ideone.com/LPkktf – odinthenerd
Hier können Sie ein gutes Beispiel sehen.Wenn Sie eine virtuelle Methode verwenden, weiß das Programm, was zur Laufzeit ausgeführt wird. Die Implementierung von CRTP ist der Compiler, der in der Kompilierzeit entscheidet !!! Das ist eine großartige Leistung! Diese
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);
}
};
Konnte man das nicht tun, indem man 'virtual void schreiben (const char * str) const = 0; Obwohl, um fair zu sein, scheint diese Technik sehr hilfreich, wenn 'write' andere Arbeit macht. – atlex2
Mit einer reinen virtuellen Methode lösen Sie die Vererbung in der Laufzeit statt in der Kompilierzeit. CRTP wird verwendet, um dies in der Kompilierzeit zu lösen, damit die Ausführung schneller wird. – GutiMac
ist keine direkte Antwort, sondern ein Beispiel dafür, wie CRTP nützlich sein kann.
Ein gutes konkretes Beispiel CRTP iststd::enable_shared_from_this
von C++ 11:
Eine Klasse von
T
enable_shared_from_this<T>
erben können dieshared_from_this
Elementfunktionen zu erben, die eine erhaltenshared_ptr
Instanz, die auf*this
zeigt.
Das heißt, von std::enable_shared_from_this
vererben macht es möglich, einen gemeinsamen (oder schwach) Zeiger auf der Instanz, ohne den Zugriff darauf zu erhalten (zum Beispiel von einem Mitglied Funktion, wo man nur über *this
wissen).
Es ist nützlich, wenn Sie ein std::shared_ptr
geben müssen, aber Sie nur Zugriff auf *this
haben:
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));
}
};
Der Grund, warum Sie nicht nur this
direkt statt shared_from_this()
passieren kann ist, dass es den Besitz Mechanismus brechen würde:
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);
CRTP ist eine Technik zum Implementieren von Kompilierungszeitpolymorphismus. Hier ist ein sehr einfaches Beispiel. Im folgenden Beispiel arbeitet ProcessFoo()
mit der Klassenschnittstelle Base
und Base::Foo
ruft die Methode foo()
des abgeleiteten Objekts auf, was Sie mit virtuellen Methoden erreichen wollen.
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;
}
Ausgang:
derived foo
AnotherDerived foo
lesen CRTP Fragen auf SO: http://stackoverflow.com/questions/tagged/crtp. Das könnte dir eine Idee geben. – sbi
@sbi: Wenn er das tut, wird er seine eigene Frage finden. Und das wäre seltsam wiederkehrend. :) –
Übrigens scheint mir der Begriff "neugierig rekursiv" zu sein. Verkenne ich die Bedeutung? –