Дивно рекурсивний шаблонДивно рекурсивний шаблон (англ. curiously recurring template pattern (CRTP)) — ідіома в мові програмування C++, коли клас Загальна формаtemplate <typename T>
struct base
{
// ...
};
struct derived : base<derived>
{
// ...
};
Цей підхід можна використати для реалізації статичного поліморфізму, а також в деяких інших техніках метапрограмування як описано Андрієм Александреску в його книзі — Сучасне проєктування на C++.[2] Статичний поліморфізм#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 дехто називає «симулюванням динамічного зв'язування».[3] Цей підхід широко використовується в 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>
{
// ...
};
Ланцюг методівДля спрощення багаторазового виклику методів одного об'єкта в об'єктно-орієнтованих мовах програмування існує популярний прийом, відомий як англ. 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'. Дивно рекурсивний шаблон може бути використаний щоб запобігти вказаній проблемі і реалізувати «Поліморфний ланцюг»[4]: // Базовий клас
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 на екземпляр[5]. Примітки
Див. також |
Portal di Ensiklopedia Dunia