Kann jemand, ohne sich auf ein Buch zu beziehen, eine gute Erklärung für CRTP
ein Codebeispiel geben?
Kann jemand, ohne sich auf ein Buch zu beziehen, eine gute Erklärung für CRTP
ein Codebeispiel geben?
Antworten:
Kurz gesagt, CRTP ist, wenn eine Klasse A
eine Basisklasse hat, die eine Vorlagenspezialisierung für die Klasse A
selbst ist. Z.B
template <class T>
class X{...};
class A : public X<A> {...};
Es kommt merkwürdigerweise immer wieder vor, nicht wahr? :) :)
Was gibt dir das? Dies gibt der X
Vorlage tatsächlich die Möglichkeit, eine Basisklasse für ihre Spezialisierungen zu sein.
Sie können beispielsweise eine generische Singleton-Klasse (vereinfachte Version) wie diese erstellen
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 eine beliebige Klasse zu A
einem Singleton 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 einen beliebigen Typ X
von singleton<X>
allen (öffentlichen, geschützten) Mitgliedern geerbt wird, 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 vorhanden sind, diese Logik jedoch in einer separaten Vorlage kapseln möchten (die Idee für eine konkrete Klasse ist recht einfach - haben Sie eine statische Variable, Inkrement in ctors, Dekrement in dtors ). Versuchen Sie es als Übung!
Ein weiteres nützliches Beispiel für Boost (ich bin nicht sicher, wie sie es implementiert haben, aber CRTP wird es auch tun). Stellen Sie sich vor, Sie möchten nur einen Operator <
für Ihre Klassen bereitstellen , aber automatisch einen Operator ==
für sie!
Sie könnten es so machen:
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 <
}
Jetzt können Sie es so verwenden
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
Jetzt haben Sie keinen expliziten Operator ==
für angegeben Apple
? Aber du hast es! Du kannst 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ürde , wenn man nur Operator schrieb ==
für Apple
, aber vorstellen , dass die Equality
Vorlage nicht nur würde , ==
sondern >
, >=
, <=
etc. Und Sie können diese Definitionen für verwenden mehrere Klassen, die Wiederverwendung von Code!
CRTP ist eine wunderbare Sache :) HTH
Hier sehen Sie ein gutes Beispiel. Wenn Sie eine virtuelle Methode verwenden, weiß das Programm, was zur Laufzeit ausgeführt wird. Bei der Implementierung von CRTP entscheidet der Compiler in der Kompilierungszeit !!! Das ist eine großartige Leistung!
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;
? Um fair zu sein, scheint diese Technik bei write
anderen Arbeiten sehr hilfreich zu sein .
CRTP ist eine Technik zur Implementierung des Polymorphismus zur Kompilierungszeit. Hier ist ein sehr einfaches Beispiel. Im folgenden Beispiel ProcessFoo()
wird mit der Base
Klassenschnittstelle gearbeitet und Base::Foo
die foo()
Methode des abgeleiteten Objekts aufgerufen. Dies ist das Ziel, das Sie mit virtuellen Methoden erreichen möchten.
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;
}
Ausgabe:
derived foo
AnotherDerived foo
foo()
sie von der abgeleiteten Klasse implementiert wird.
ProcessFoo()
Funktion nützlich ist.
void ProcessFoo(T* b)
und ohne Derived und AnotherDerived immer noch funktionieren würde. IMHO wäre es interessanter, wenn ProcessFoo keine Vorlagen irgendwie verwenden würde.
ProcessFoo()
mit jedem Typ, der die Schnittstelle implementiert, dh in diesem Fall sollte der Eingabetyp T eine Methode namens haben foo()
. Zweitens ProcessFoo
würden Sie wahrscheinlich RTTI verwenden, um eine Nicht-Vorlage für die Arbeit mit mehreren Typen zu erhalten, was wir vermeiden möchten. Darüber hinaus bietet die Vorlagenversion eine Überprüfung der Kompilierungszeit auf der Benutzeroberfläche.
Dies ist keine direkte Antwort, sondern ein Beispiel dafür, wie nützlich CRTP sein kann.
Ein gutes konkretes Beispiel für CRTP ist std::enable_shared_from_this
C ++ 11:
Eine Klasse
T
kann von erbenenable_shared_from_this<T>
, um die Elementfunktionen zu erbenshared_from_this
, auf die eineshared_ptr
Instanz verweist, auf die verwiesen wird*this
.
Das heißt, das Erben von std::enable_shared_from_this
ermöglicht es, einen gemeinsam genutzten (oder schwachen) Zeiger auf Ihre Instanz zu erhalten, ohne darauf zugreifen zu können (z. B. von einer Mitgliedsfunktion, von der Sie nur wissen *this
).
Es ist nützlich, wenn Sie eine geben müssen, std::shared_ptr
aber nur Zugriff auf *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));
}
};
Der Grund, warum Sie nicht einfach this
direkt passieren können, shared_from_this()
ist, dass dies den Eigentumsmechanismus 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);
Nur als Hinweis:
CRTP könnte verwendet werden, um statischen Polymorphismus zu implementieren (der dynamischen Polymorphismus mag, jedoch ohne virtuelle Funktionszeigertabelle).
#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;
}
Die Ausgabe wäre:
Derived1 method
Derived2 method
vtable
ohne CRTP ohne s ausgeführt werden. Was vtable
wirklich bietet, ist die Verwendung der Basisklasse (Zeiger oder Referenz) zum Aufrufen abgeleiteter Methoden. Hier sollten Sie zeigen, wie es mit CRTP gemacht wird.
Base<>::method ()
es nicht einmal aufgerufen, und Sie verwenden nirgendwo Polymorphismus.
methodImpl
den Namen method
von Base
und in abgeleiteten Klassen methodImpl
anstelle vonmethod