Дивно рекурсивний шаблон

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до: навігація, пошук

Дивно рекурсивний шаблон (англ. curiously recurring template pattern (CRTP)) — це підхід в мові програмування C++, в якому клас X є похідним від шаблону класу, інстанційованого із використанням самого X як шаблонного аргументу. Ім'я цього підходу було винайдене Джимом Копліном,[1], який розглянув його в одному з найперших шаблонних кодів на 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] [5] :

// Базовий клас
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!");

Примітки[ред.ред. код]

  1. Coplien, James O. (1995, February). Curiously Recurring Template Patterns. C++ Report: 24–27. 
  2. Alexandrescu, Andrei (2001). Сучасне проектування на C++: Generic Programming and Design Patterns Applied. Addison-Wesley. ISBN 0-201-70431-5. 
  3. Simulated Dynamic Binding. 2003-05-07. Архів оригіналу за 2013-07-21. Процитовано 2012-01-13. 
  4. Arena, Marco. Use CRTP for polymorphic chaining. Процитовано 15-03-2017. 
  5. Vladlen, IT українською. Повертаємо дочірній клас з батьківського. Факультатив. Процитовано 24-02-2018.