Дивно рекурсивний шаблон () — ідіома в мові програмування C++, коли клас X є похідним від шаблону класу, інстанційованого із використанням самого X як шаблонного аргументу. Назва для цієї ідіоми була запропонована Джимом Копліном,, котрий розглянув її в одному з найперших шаблонних кодів на C++.
Загальна форма
template <typename T> struct base { // ... }; struct derived : base<derived> { // ...
Цей підхід можна використати для реалізації статичного поліморфізму, а також в деяких інших техніках метапрограмування як описано Андрієм Александреску в його книзі — Сучасне проєктування на C++.
Статичний поліморфізм #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 і WTL.
Лічильник об'єктів
Головна ціль лічильника об'єктів — отримання статистики створення й руйнування об'єктів даного класу. Це можна легко реалізувати із використанням 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> { // ...
Ланцюг методів
Для спрощення багаторазового виклику методів одного об'єкта в об'єктно-орієнтованих мовах програмування існує популярний прийом, відомий як (ланцюг методів). Кожен з цих методів повертає об'єкт, дозволяючи тим самим виклик методів послідовно в одному виразі, без створення додаткових змінних для збереження тимчасових чи проміжних результатів.
Однак у випадках, коли цей прийом застосувати до об'єктів, що мають ієрархію, виникає ускладнення. Припустимо ми маємо наступний базовий клас:
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 на екземпляр.
Примітки
Див. також Шаблони (C++)