Czy bez odwoływania się do książki ktoś mógłby podać dobre wyjaśnienie na CRTP
przykładzie kodu?
Czy bez odwoływania się do książki ktoś mógłby podać dobre wyjaśnienie na CRTP
przykładzie kodu?
Odpowiedzi:
W skrócie, CRTP ma miejsce, gdy klasa A
ma klasę podstawową, która jest specjalizacją szablonu dla A
samej 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 X
szablonowi 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ę A
singlem, 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 X
zostanie 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 Equality
szablon 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 write
wykonuje inne prace.
CRTP to technika implementacji polimorfizmu w czasie kompilacji. Oto bardzo prosty przykład. W poniższym przykładzie ProcessFoo()
pracuje z Base
interfejsem klasy i Base::Foo
wywoł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 ProcessFoo
pracy 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_this
z C ++ 11:
Klasa
T
może dziedziczyć funkcjeenable_shared_from_this<T>
dziedziczące,shared_from_this
które uzyskująshared_ptr
instancję wskazującą*this
.
Oznacza to, że dziedziczenie po std::enable_shared_from_this
umoż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_ptr
ale 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ć this
bezpoś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, vtable
co 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.
methodImpl
w method
z Base
i w klasach pochodnych wymienić methodImpl
zamiastmethod