Czy bez odwoływania się do książki ktoś mógłby podać dobre wyjaśnienie na CRTPprzykładzie kodu?
Czy bez odwoływania się do książki ktoś mógłby podać dobre wyjaśnienie na CRTPprzykładzie kodu?
Odpowiedzi:
W skrócie, CRTP ma miejsce, gdy klasa Ama klasę podstawową, która jest specjalizacją szablonu dla Asamej klasy . Na przykład
template <class T>
class X{...};
class A : public X<A> {...};
To jest ciekawie powtarzające się, prawda? :)
Co ci to daje? Daje to Xszablonowi możliwość bycia klasą bazową dla jego specjalizacji.
Na przykład, możesz stworzyć ogólną klasę singleton (wersja uproszczona) w ten sposób
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;
Teraz, aby uczynić arbitralną klasę Asinglem, powinieneś to zrobić
class A: public Singleton<A>
{
//Rest of functionality for class A
};
Więc widzisz? Szablon singletonu zakłada, że jego specjalizacja dla dowolnego typu Xzostanie odziedziczona, singleton<X>a zatem wszystkie jego (publiczne, chronione) elementy będą dostępne, w tym GetInstance! Istnieją inne przydatne zastosowania CRTP. Na przykład, jeśli chcesz policzyć wszystkie instancje, które obecnie istnieją dla twojej klasy, ale chcesz zawrzeć tę logikę w osobnym szablonie (pomysł na konkretną klasę jest dość prosty - mieć zmienną statyczną, przyrost w ctre, spadek w dtr ). Spróbuj to zrobić jako ćwiczenie!
Kolejny przydatny przykład dla Boost (nie jestem pewien, jak go zaimplementowali, ale CRTP też to zrobi). Wyobraź sobie, że chcesz zapewnić tylko operator <dla swoich klas, ale automatycznie ==dla nich!
możesz to zrobić w następujący sposób:
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 <
}
Teraz możesz używać tego w ten sposób
struct Apple:public Equality<Apple>
{
int size;
};
bool operator < (Apple const & a1, Apple const& a2)
{
return a1.size < a2.size;
}
Teraz nie przewidują explicite operatora ==za Apple? Ale ty to masz! Możesz pisać
int main()
{
Apple a1;
Apple a2;
a1.size = 10;
a2.size = 10;
if(a1 == a2) //the compiler won't complain!
{
}
}
To może wydawać się, że można napisać mniej, jeśli tylko napisali operatora ==za Apple, ale wyobraźmy sobie, że Equalityszablon będzie nie tylko zapewniają ==jednak >, >=, <=itd. I można użyć tych definicji dla wielu klas, ponowne użycie kodu!
CRTP to cudowna rzecz :) HTH
Tutaj możesz zobaczyć świetny przykład. Jeśli użyjesz metody wirtualnej, program będzie wiedział, co wykonać w czasie wykonywania. Implementując CRTP, kompilator decyduje o czasie kompilacji !!! To świetny występ!
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;? Chociaż, mówiąc uczciwie, technika ta wydaje się bardzo pomocna, gdy writewykonuje inne prace.
CRTP to technika implementacji polimorfizmu w czasie kompilacji. Oto bardzo prosty przykład. W poniższym przykładzie ProcessFoo()pracuje z Baseinterfejsem klasy i Base::Foowywołuje foo()metodę obiektu pochodnego , do czego dąży się metodami wirtualnymi.
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;
}
Wynik:
derived foo
AnotherDerived foo
foo()jest implementowana przez klasę pochodną.
ProcessFoo()funkcji.
void ProcessFoo(T* b)niezależnie od tego, czy rzeczywiście wyprowadziłem Derived i AnotherDerived, nadal działałoby. IMHO byłoby bardziej interesujące, gdyby ProcessFoo w jakiś sposób nie korzystało z szablonów.
ProcessFoo()będzie działał z każdym typem, który implementuje interfejs, tj. W tym przypadku typ wejściowy T powinien mieć metodę o nazwie foo(). Po drugie, aby uzyskać szablon bez ProcessFoopracy z wieloma typami, prawdopodobnie użyłbyś RTTI, a tego chcemy uniknąć. Co więcej, wersja szablonowa zapewnia kontrolę czasu kompilacji interfejsu.
To nie jest bezpośrednia odpowiedź, ale raczej przykład przydatności CRTP .
Dobry konkretny przykład CRTP pochodzi std::enable_shared_from_thisz C ++ 11:
Klasa
Tmoże dziedziczyć funkcjeenable_shared_from_this<T>dziedziczące,shared_from_thisktóre uzyskująshared_ptrinstancję wskazującą*this.
Oznacza to, że dziedziczenie po std::enable_shared_from_thisumożliwia uzyskanie udostępnionego (lub słabego) wskaźnika do instancji bez dostępu do niego (np. Z funkcji członka, o której wiesz tylko *this).
Jest to przydatne, gdy musisz podać, std::shared_ptrale masz dostęp tylko do *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));
}
};
Powodem, dla którego nie można po prostu przekazać thisbezpośrednio, shared_from_this()jest to, że zepsułoby to mechanizm własności:
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);
Podobnie jak uwaga:
CRTP może być wykorzystany do implementacji statycznego polimorfizmu (który lubi dynamiczny polimorfizm, ale bez tablicy wskaźników funkcji wirtualnej).
#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;
}
Dane wyjściowe będą:
Derived1 method
Derived2 method
vtableżadnych znaków bez użycia CRTP. To, vtableco naprawdę zapewnia, to używanie klasy bazowej (wskaźnika lub odwołania) do wywoływania metod pochodnych. W tym miejscu powinieneś pokazać, jak to się robi z CRTP.
Base<>::method ()nie jest nawet wywoływany ani nigdzie nie używasz polimorfizmu.
methodImplw methodz Basei w klasach pochodnych wymienić methodImplzamiastmethod