Дивно рекурсивний шаблон (англ. curiously recurring template pattern (CRTP)) — це підхід в мові програмування , в якому клас X
є похідним від класу, інстанційованого із використанням самого X
як шаблонного аргументу. Ім'я цього підходу було винайдене Джимом Копліном,, який розглянув його в одному з найперших шаблонних кодів на C++.
Загальна форма
template <typename T> struct base { // ... }; struct derived : base<derived> { // ... };
Цей підхід можна використати для реалізації статичного поліморфізму, а також в деяких інших техніках метапрограмування як описано Андрієм Александреску в його книзі — .
Статичний поліморфізм
#include <iostream> using namespace std; template<typename Derived> struct Base { void foo() { static_cast<Derived*>(this)->foo(); } }; struct A : Base<A> { void foo() { cout << "A::foo();" << endl; } }; struct B : Base<B> { void foo() { cout << "B::foo();" << endl; } }; template<typename T> void bar(Base<T>& base) { base.foo(); } int main() { A a; B b; bar(a); bar(b); return 0; }
Цей підхід дає ефект подібний до використання віртуальних функцій, без ціни (і деякої гнучкості) динамічного поліморфізму. Це використання CRTP дехто називає «симулюванням динамічного зв'язування». Цей підхід широко використовується в Windows бібліотеках ATL і .
Лічильник об'єктів
Головна ціль лічильника об'єктів — отримання статистики створення й руйнування об'єктів даного класу. Це можна легко реалізувати із використанням CRTP:
template <typename T> struct counter { static int objects_created; static int objects_alive; counter() { ++objects_created; ++objects_alive; } protected: ~counter() { --objects_alive; } }; template <typename T> int counter<T>::objects_created( 0 ); template <typename T> int counter<T>::objects_alive( 0 ); class X : counter<X> { // ... }; class Y : counter<Y> { // ... };
Ланцюг методів
Для спрощення багаторазового виклику методів одного об'єкта в об'єктно-орієнтованих мовах програмування існує популярний прийом, відомий як англ. method chaining (ланцюг методів). Кожен з цих методів повертає об'єкт, дозволяючи тим самим виклик методів послідовно в одному виразі, без створення додаткових змінних для збереження тимчасових чи проміжних результатів.
Однак у випадках, коли цей прийом застосувати до об'єктів, що мають ієрархію, виникає ускладнення. Припустимо ми маємо наступний базовий клас:
class Printer { public: Printer(ostream& pstream) : m_stream(pstream) {} template <typename T> Printer& print(T&& t) { m_stream << t; return *this; } template <typename T> Printer& println(T&& t) { m_stream << t << endl; return *this; } private: ostream& m_stream; };
Його методи можуть бути легко викликані по ланцюгу:
Printer{myStream}.println("hello").println(500);
Однак, коли ми задекларуємо похідний клас:
class CoutPrinter : public Printer { public: CoutPrinter() : Printer(cout) {} CoutPrinter& SetConsoleColor(Color c) { ... return *this; } };
то «втратимо» інформацію про клас, при спробі викликати метод базового класу:
CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!"); // помилка компіляції, бо ми тут маємо об'єкт 'Printer', а не 'CoutPrinter'
Це трапилось тому, що 'print' є функцією базового класу — 'Printer', вона повертає екземпляр класу 'Printer'.
Дивно рекурсивний шаблон може бути використаний щоб запобігти вказаній проблемі і реалізувати «Поліморфний ланцюг»:
// Базовий клас template <typename ConcretePrinter> class Printer { public: Printer(ostream& pstream) : m_stream(pstream) {} template <typename T> ConcretePrinter& print(T&& t) { m_stream << t; return static_cast<ConcretePrinter&>(*this); } template <typename T> ConcretePrinter& println(T&& t) { m_stream << t << endl; return static_cast<ConcretePrinter&>(*this); } private: ostream& m_stream; }; // Похідний клас class CoutPrinter : public Printer<CoutPrinter> { public: CoutPrinter() : Printer(cout) {} CoutPrinter& SetConsoleColor(Color c) { ... return *this; } }; // використання CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!");
Домішки
Дивно рекурсивний шаблон також може бути використаний для реалізації домішок. Поширеним прикладом є клас std::enable_shared_from_this, породжені класи від якого здатні повертати std::shared_ptr на свої екземпляри.
Тобто, якщо клас MySharedClass породжений від public std::enable_shared_from_this то він матиме метод shared_from_this, який повертатиме std::shared_ptr на екземпляр.
Примітки
- Coplien, James O. (1995, February). Curiously Recurring Template Patterns. C++ Report: 24—27.
- (2001). : Generic Programming and Design Patterns Applied. Addison-Wesley. ISBN .
- Simulated Dynamic Binding. 7 травня 2003. Архів оригіналу за 20 липня 2013. Процитовано 13 січня 2012.
- Arena, Marco. . Архів оригіналу за 27 лютого 2018. Процитовано 15-03-2017.
- Rainer Grimm (13 лютого 2017). . Архів оригіналу за 18 грудня 2019. Процитовано 2 січня 2020.
Див. також
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Divno rekursivnij shablon angl curiously recurring template pattern CRTP ce pidhid v movi programuvannya C v yakomu klas X ye pohidnim vid shablonu klasu instancijovanogo iz vikoristannyam samogo X yak shablonnogo argumentu Im ya cogo pidhodu bulo vinajdene Dzhimom Koplinom yakij rozglyanuv jogo v odnomu z najpershih shablonnih kodiv na C Zagalna formatemplate lt typename T gt struct base struct derived base lt derived gt Cej pidhid mozhna vikoristati dlya realizaciyi statichnogo polimorfizmu a takozh v deyakih inshih tehnikah metaprogramuvannya yak opisano Andriyem Aleksandresku v jogo knizi Suchasne proyektuvannya na C Statichnij polimorfizm include lt iostream gt using namespace std template lt typename Derived gt struct Base void foo static cast lt Derived gt this gt foo struct A Base lt A gt void foo cout lt lt A foo lt lt endl struct B Base lt B gt void foo cout lt lt B foo lt lt endl template lt typename T gt void bar Base lt T gt amp base base foo int main A a B b bar a bar b return 0 Cej pidhid daye efekt podibnij do vikoristannya virtualnih funkcij bez cini i deyakoyi gnuchkosti dinamichnogo polimorfizmu Ce vikoristannya CRTP dehto nazivaye simulyuvannyam dinamichnogo zv yazuvannya Cej pidhid shiroko vikoristovuyetsya v Windows bibliotekah ATL i Lichilnik ob yektivGolovna cil lichilnika ob yektiv otrimannya statistiki stvorennya j rujnuvannya ob yektiv danogo klasu Ce mozhna legko realizuvati iz vikoristannyam CRTP template lt typename T gt struct counter static int objects created static int objects alive counter objects created objects alive protected counter objects alive template lt typename T gt int counter lt T gt objects created 0 template lt typename T gt int counter lt T gt objects alive 0 class X counter lt X gt class Y counter lt Y gt Lancyug metodivDlya sproshennya bagatorazovogo vikliku metodiv odnogo ob yekta v ob yektno oriyentovanih movah programuvannya isnuye populyarnij prijom vidomij yak angl method chaining lancyug metodiv Kozhen z cih metodiv povertaye ob yekt dozvolyayuchi tim samim viklik metodiv poslidovno v odnomu virazi bez stvorennya dodatkovih zminnih dlya zberezhennya timchasovih chi promizhnih rezultativ Odnak u vipadkah koli cej prijom zastosuvati do ob yektiv sho mayut iyerarhiyu vinikaye uskladnennya Pripustimo mi mayemo nastupnij bazovij klas class Printer public Printer ostream amp pstream m stream pstream template lt typename T gt Printer amp print T amp amp t m stream lt lt t return this template lt typename T gt Printer amp println T amp amp t m stream lt lt t lt lt endl return this private ostream amp m stream Jogo metodi mozhut buti legko viklikani po lancyugu Printer myStream println hello println 500 Odnak koli mi zadeklaruyemo pohidnij klas class CoutPrinter public Printer public CoutPrinter Printer cout CoutPrinter amp SetConsoleColor Color c return this to vtratimo informaciyu pro klas pri sprobi viklikati metod bazovogo klasu CoutPrinter print Hello SetConsoleColor Color red println Printer pomilka kompilyaciyi bo mi tut mayemo ob yekt Printer a ne CoutPrinter Ce trapilos tomu sho print ye funkciyeyu bazovogo klasu Printer vona povertaye ekzemplyar klasu Printer Divno rekursivnij shablon mozhe buti vikoristanij shob zapobigti vkazanij problemi i realizuvati Polimorfnij lancyug Bazovij klas template lt typename ConcretePrinter gt class Printer public Printer ostream amp pstream m stream pstream template lt typename T gt ConcretePrinter amp print T amp amp t m stream lt lt t return static cast lt ConcretePrinter amp gt this template lt typename T gt ConcretePrinter amp println T amp amp t m stream lt lt t lt lt endl return static cast lt ConcretePrinter amp gt this private ostream amp m stream Pohidnij klas class CoutPrinter public Printer lt CoutPrinter gt public CoutPrinter Printer cout CoutPrinter amp SetConsoleColor Color c return this vikoristannya CoutPrinter print Hello SetConsoleColor Color red println Printer DomishkiDivno rekursivnij shablon takozh mozhe buti vikoristanij dlya realizaciyi domishok Poshirenim prikladom ye klas std enable shared from this porodzheni klasi vid yakogo zdatni povertati std shared ptr na svoyi ekzemplyari Tobto yaksho klas MySharedClass porodzhenij vid public std enable shared from this to vin matime metod shared from this yakij povertatime std shared ptr na ekzemplyar PrimitkiCoplien James O 1995 February Curiously Recurring Template Patterns C Report 24 27 2001 Suchasne proyektuvannya na C Generic Programming and Design Patterns Applied Addison Wesley ISBN 0 201 70431 5 Simulated Dynamic Binding 7 travnya 2003 Arhiv originalu za 20 lipnya 2013 Procitovano 13 sichnya 2012 Arena Marco Arhiv originalu za 27 lyutogo 2018 Procitovano 15 03 2017 Rainer Grimm 13 lyutogo 2017 Arhiv originalu za 18 grudnya 2019 Procitovano 2 sichnya 2020 Div takozhShabloni C