Теперь, чтобы сделать произвольный класс Aсинглтоном, вы должны сделать это
classA:public Singleton<A>
{
//Rest of functionality for class A
};
Так ты видишь? Шаблон singleton предполагает, что его специализация для любого типа Xбудет унаследована от singleton<X>и, следовательно, будет иметь доступ ко всем его (общедоступным, защищенным) членам, включая GetInstance! Есть и другие полезные применения CRTP. Например, если вы хотите подсчитать все экземпляры, которые в настоящее время существуют для вашего класса, но хотите инкапсулировать эту логику в отдельный шаблон (идея для конкретного класса довольно проста - иметь статическую переменную, приращение в ctors, уменьшение в dtors ). Попробуйте сделать это как упражнение!
Еще один полезный пример для Boost (я не уверен, как они его реализовали, но CRTP тоже подойдет). Представьте, что вы хотите предоставить только оператор <для своих классов, но автоматически оператор ==для них!
вы могли бы сделать это так:
template<classDerived>
classEquality
{
};
template <classDerived>
booloperator == (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 <
}
Это может показаться , что вы написали бы меньше , если вы просто написали оператор ==для Apple, но представьте себе , что Equalityшаблон будет обеспечивать не только , ==но >, >=, и <=т.д. И вы можете использовать эти определения для нескольких классов, повторным использованием кода!
Этот пост не защищает синглтон как хороший шаблон программирования. Он просто использует его как иллюстрацию, которая может быть понятна.
Ибо
3
@Armen: Ответ объясняет CRTP таким образом, чтобы его можно было четко понять, это хороший ответ, спасибо за такой хороший ответ.
Alok Save
1
@Armen: спасибо за отличное объяснение. Раньше я как бы не получал CRTP, но пример с равенством меня проясняет! +1
Пол
1
Еще один пример использования CRTP - это когда вам нужен некопируемый класс: template <class T> class NonCopyable {protected: NonCopyable () {} ~ NonCopyable () {} private: NonCopyable (const NonCopyable &); NonCopyable & operator = (const NonCopyable &); }; Затем вы используете некопируемый, как показано ниже: class Mutex: private NonCopyable <Mutex> {public: void Lock () {} void UnLock () {}};
Вирен
2
@Puppy: Синглтон не страшен. Программисты уровня ниже среднего используют его слишком часто, когда другие подходы были бы более подходящими, но то, что большинство его применений ужасно, не делает сам шаблон ужасным. Бывают случаи, когда синглтон - лучший вариант, хотя и редко.
Kaiserludi
49
Вот отличный пример. Если вы используете виртуальный метод, программа будет знать, что выполняется во время выполнения. Реализуя CRTP, компилятор решает во время компиляции !!! Это отличный спектакль!
Не могли бы вы сделать это с помощью определения virtual void write(const char* str) const = 0;? Хотя, честно говоря, эта техника кажется очень полезной при writeвыполнении другой работы.
atlex2 09
26
Используя чисто виртуальный метод, вы решаете наследование во время выполнения, а не во время компиляции. CRTP используется для решения этой проблемы во время компиляции, поэтому выполнение будет быстрее.
GutiMac
2
Попробуйте создать простую функцию, ожидающую абстрактного Writer: вы не можете этого сделать, потому что нигде нет класса с именем Writer, так где же именно ваш полиморфизм? Это совсем не эквивалентно виртуальным функциям и гораздо менее полезно.
27
CRTP - это метод реализации полиморфизма времени компиляции. Вот очень простой пример. В приведенном ниже примере ProcessFoo()он работает с Baseинтерфейсом класса и Base::Fooвызывает производный объектfoo() , что и нужно делать с виртуальными методами.
В этом примере также может быть стоит добавить пример того, как реализовать foo () по умолчанию в базовом классе, который будет вызываться, если ни один из Derived не реализовал его. AKA измените foo в Base на какое-то другое имя (например, caller ()), добавьте новую функцию foo () в Base, которая cout's "Base". Затем вызовите caller () внутри ProcessFoo
wizurd
@wizurd Этот пример больше иллюстрирует функцию чистого виртуального базового класса, т.е. мы обеспечиваем foo()ее реализацию производным классом.
blueskin
4
Это мой любимый ответ, поскольку он также показывает, почему этот шаблон полезен для ProcessFoo()функции.
Пьетро
Я не понимаю смысла этого кода, потому что с void ProcessFoo(T* b)фактическим производным Derived и AnotherDerived и без них он все равно будет работать. ИМХО было бы интереснее, если бы ProcessFoo как-то не использовал шаблоны.
Габриэль Девиллерс,
3
@GabrielDevillers Во-первых, шаблонизатор ProcessFoo()будет работать с любым типом, реализующим интерфейс, т.е. в этом случае тип ввода T должен иметь вызываемый метод foo(). Во-вторых, для того, чтобы не использовать шаблон ProcessFooдля работы с несколькими типами, вы, скорее всего, в конечном итоге будете использовать RTTI, чего мы хотим избежать. Кроме того, в шаблонной версии вы можете проверить время компиляции в интерфейсе.
blueskin
6
Это не прямой ответ, а скорее пример того, как CRTP может быть полезен.
Пример из хорошо бетона CRTP это std::enable_shared_from_thisот C ++ 11:
Класс Tможет наследовать от, enable_shared_from_this<T>чтобы наследовать shared_from_thisфункции-члены, которые получают shared_ptrэкземпляр, указывающий на *this.
То есть наследование от std::enable_shared_from_thisпозволяет получить общий (или слабый) указатель на ваш экземпляр без доступа к нему (например, из функции-члена, о которой вы знаете только*this ).
Это полезно, когда вам нужно предоставить, std::shared_ptrно у вас есть доступ только к *this:
CRTP может использоваться для реализации статического полиморфизма (который похож на динамический полиморфизм, но без таблицы указателей виртуальных функций).
извините, моя ошибка, static_cast позаботится об изменении. Если вы все равно хотите увидеть угловой корпус, даже если он не вызывает ошибок, см. Здесь: ideone.com/LPkktf
odinthenerd 06
31
Плохой пример. Этот код может быть выполнен без vtables без использования CRTP. Что vtableдействительно обеспечивает, так это использование базового класса (указателя или ссылки) для вызова производных методов. Здесь вы должны показать, как это делается с помощью CRTP.
Etherealone
18
В вашем примере Base<>::method ()даже не вызывается, и вы нигде не используете полиморфизм.
MikeMB
1
@Jichao, согласно примечанию @MikeMB, вы должны вызывать methodImplимя methodиз Baseи в производных классах methodImplвместоmethod
Иван Куш
1
если вы используете аналогичный метод (), то он статически привязан, и вам не нужен общий базовый класс. Потому что в любом случае вы не можете использовать его полиморфно через указатель базового класса или ref. Поэтому код должен выглядеть так: #include <iostream> template <typename T> struct Writer {void write () {static_cast <T *> (this) -> writeImpl (); }}; struct Derived1: public Writer <Derived1> {void writeImpl () {std :: cout << "D1"; }}; struct Derived2: public Writer <Derived2> {void writeImpl () {std :: cout << "DER2"; }};
Ответы:
Короче говоря, CRTP - это когда у класса
A
есть базовый класс, который является специализацией шаблона для самого классаA
. Напримерtemplate <class T> class X{...}; class A : public X<A> {...};
Это будет любопытно повторяющееся, не так ли? :)
Что это дает вам? Это фактически дает
X
шаблону возможность быть базовым классом для своих специализаций.Например, вы можете создать общий одноэлементный класс (упрощенная версия), подобный этому
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;
Теперь, чтобы сделать произвольный класс
A
синглтоном, вы должны сделать этоclass A: public Singleton<A> { //Rest of functionality for class A };
Так ты видишь? Шаблон singleton предполагает, что его специализация для любого типа
X
будет унаследована отsingleton<X>
и, следовательно, будет иметь доступ ко всем его (общедоступным, защищенным) членам, включаяGetInstance
! Есть и другие полезные применения CRTP. Например, если вы хотите подсчитать все экземпляры, которые в настоящее время существуют для вашего класса, но хотите инкапсулировать эту логику в отдельный шаблон (идея для конкретного класса довольно проста - иметь статическую переменную, приращение в ctors, уменьшение в dtors ). Попробуйте сделать это как упражнение!Еще один полезный пример для Boost (я не уверен, как они его реализовали, но CRTP тоже подойдет). Представьте, что вы хотите предоставить только оператор
<
для своих классов, но автоматически оператор==
для них!вы могли бы сделать это так:
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 < }
Теперь вы можете использовать это так
struct Apple:public Equality<Apple> { int size; }; bool operator < (Apple const & a1, Apple const& a2) { return a1.size < a2.size; }
Итак, вы явно не указали оператор
==
дляApple
? Но оно у вас есть! Ты можешь написатьint main() { Apple a1; Apple a2; a1.size = 10; a2.size = 10; if(a1 == a2) //the compiler won't complain! { } }
Это может показаться , что вы написали бы меньше , если вы просто написали оператор
==
дляApple
, но представьте себе , чтоEquality
шаблон будет обеспечивать не только ,==
но>
,>=
, и<=
т.д. И вы можете использовать эти определения для нескольких классов, повторным использованием кода!CRTP - замечательная штука :) HTH
источник
Вот отличный пример. Если вы используете виртуальный метод, программа будет знать, что выполняется во время выполнения. Реализуя CRTP, компилятор решает во время компиляции !!! Это отличный спектакль!
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;
? Хотя, честно говоря, эта техника кажется очень полезной приwrite
выполнении другой работы.CRTP - это метод реализации полиморфизма времени компиляции. Вот очень простой пример. В приведенном ниже примере
ProcessFoo()
он работает сBase
интерфейсом класса иBase::Foo
вызывает производный объектfoo()
, что и нужно делать с виртуальными методами.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; }
Выход:
источник
foo()
ее реализацию производным классом.ProcessFoo()
функции.void ProcessFoo(T* b)
фактическим производным Derived и AnotherDerived и без них он все равно будет работать. ИМХО было бы интереснее, если бы ProcessFoo как-то не использовал шаблоны.ProcessFoo()
будет работать с любым типом, реализующим интерфейс, т.е. в этом случае тип ввода T должен иметь вызываемый методfoo()
. Во-вторых, для того, чтобы не использовать шаблонProcessFoo
для работы с несколькими типами, вы, скорее всего, в конечном итоге будете использовать RTTI, чего мы хотим избежать. Кроме того, в шаблонной версии вы можете проверить время компиляции в интерфейсе.Это не прямой ответ, а скорее пример того, как CRTP может быть полезен.
Пример из хорошо бетона CRTP это
std::enable_shared_from_this
от C ++ 11:То есть наследование от
std::enable_shared_from_this
позволяет получить общий (или слабый) указатель на ваш экземпляр без доступа к нему (например, из функции-члена, о которой вы знаете только*this
).Это полезно, когда вам нужно предоставить,
std::shared_ptr
но у вас есть доступ только к*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)); } };
Причина, по которой вы не можете просто передать
this
напрямую,shared_from_this()
заключается в том, что это нарушит механизм владения: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 может использоваться для реализации статического полиморфизма (который похож на динамический полиморфизм, но без таблицы указателей виртуальных функций).
#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; }
Результатом будет:
источник
vtable
s без использования CRTP. Чтоvtable
действительно обеспечивает, так это использование базового класса (указателя или ссылки) для вызова производных методов. Здесь вы должны показать, как это делается с помощью CRTP.Base<>::method ()
даже не вызывается, и вы нигде не используете полиморфизм.methodImpl
имяmethod
изBase
и в производных классахmethodImpl
вместоmethod