0Pricing
C++ Academy · Урок

Идиома CRTP

Шаблонный паттерн с рекурсивным использованием типа

«Идиома CRTP» — бесплатный урок C++ Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C++ Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C++ Academy содержит 4 уроков всего.

Что такое CRTP

Шаблон «странно рекурсивного шаблона» (CRTP) — это идиома C++, в которой класс Derived наследуется от базового шаблона, созданного с самим Derived в качестве аргумента.

  • Структура: class Derived : public Base<Derived>
  • Базовый класс узнаёт производный тип на этапе компиляции
template <typename T>
class Base {};

class Derived : public Base<Derived> {};

Основной механизм

В базовом классе можно выполнить static_cast указателя this к производному типу. Поскольку производный тип является параметром шаблона, это полностью выполняемое на этапе компиляции преобразование без затрат во время выполнения.

#include <iostream>

template <typename T>
class Base {
public:
    void interface() {
        static_cast<T*>(this)->implementation();
    }
};

class Derived : public Base<Derived> {
public:
    void implementation() { std::cout << "Derived impl\n"; }
};

int main() {
    Derived d;
    d.interface();
    return 0;
}

Почему «странно рекурсивный»

Название происходит от кажущегося парадокса: базовый класс ссылается на производный класс, который ещё не определён полностью в момент написания базового шаблона.

  • Это работает, потому что шаблоны создаются только при использовании
  • К моменту создания экземпляра Derived является полным типом

Первое практическое применение

CRTP позволяет базовому классу предоставлять повторно используемую функциональность, обращающуюся обратно к производному классу. Здесь базовый класс предоставляет print(), который делегирует вызов производному name().

#include <iostream>
#include <string>

template <typename T>
struct Named {
    void print() {
        std::cout << static_cast<T*>(this)->name() << "\n";
    }
};

struct Cat : Named<Cat> {
    std::string name() { return "Cat"; }
};

int main() {
    Cat c;
    c.print();
    return 0;
}

Без виртуальных таблиц

В отличие от полиморфизма времени выполнения, CRTP не требует ключевого слова virtual и указателя на виртуальную таблицу в каждом объекте. Диспетчеризация полностью выполняется на этапе компиляции.

  • Объекты меньше по размеру, поскольку нет скрытого vptr
  • Вызовы можно встроить

Подсчёт экземпляров

Классическое применение CRTP — счётчик объектов. Каждый производный класс автоматически получает собственный статический счётчик.

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
    ~Counter() { --count; }
};

struct Widget : Counter<Widget> {};

int main() {
    Widget a, b;
    std::cout << Widget::count << "\n";
    return 0;
}

Каждый производный класс получает собственное состояние

Поскольку Counter<Widget> и Counter<Gadget> являются разными экземплярами шаблона, каждый из них имеет независимый статический count. При использовании одного нетипового базового класса такое разделение невозможно.

#include <iostream>

template <typename T>
struct Counter {
    static inline int count = 0;
    Counter() { ++count; }
};

struct A : Counter<A> {};
struct B : Counter<B> {};

int main() {
    A a1, a2;
    B b1;
    std::cout << A::count << " " << B::count << "\n";
    return 0;
}

Здесь static_cast безопасен

Приведение вниз внутри базового класса безопасно только в том случае, если каждый объект типа Base<T> действительно является объектом типа T. CRTP гарантирует это по конструкции, поскольку T — класс, который наследуется.

  • Никогда не создавайте Base<Wrong> для несвязанного класса

Операторы сравнения с помощью CRTP

Можно синтезировать полный набор операторов сравнения на основе одного operator<, унаследовавшись от вспомогательного класса сравнения.

#include <iostream>

template <typename T>
struct Comparable {
    friend bool operator>(const T& a, const T& b) { return b < a; }
    friend bool operator==(const T& a, const T& b) { return !(a < b) && !(b < a); }
};

struct Num : Comparable<Num> {
    int v;
    Num(int x) : v(x) {}
    friend bool operator<(const Num& a, const Num& b) { return a.v < b.v; }
};

int main() {
    std::cout << (Num(3) > Num(2)) << "\n";
    return 0;
}

Защита конструктором protected

Чтобы предотвратить случайное неправильное использование базового класса, сделайте его конструктор protected, чтобы создавать его могли только производные классы.

#include <iostream>

template <typename T>
class Base {
protected:
    Base() = default;
public:
    void run() { static_cast<T*>(this)->step(); }
};

class Job : public Base<Job> {
public:
    void step() { std::cout << "step\n"; }
};

int main() {
    Job j;
    j.run();
    return 0;
}

CRTP в стандартных библиотеках

CRTP широко встречается в настоящих библиотеках C++.

  • Его использует std::enable_shared_from_this<T>
  • На него опираются многие математические библиотеки шаблонов выражений, например Eigen
  • Диапазоны и вспомогательные средства для итераторов используют похожие шаблоны

Умение распознавать структуру X : Base<X> помогает читать сложный код.

Быстрая проверка

Проверьте, насколько хорошо Вы понимаете структуру CRTP.

Итоги

Вы изучили идиому CRTP.

  • Derived : Base<Derived> передаёт базовому классу его конкретный тип
  • static_cast<T*>(this) обеспечивает диспетчеризацию на этапе компиляции
  • Нет виртуальной таблицы, объекты меньше, вызовы можно встраивать
  • Используется для счётчиков, примесей сравнения и библиотечных вспомогательных средств

Часто задаваемые вопросы

Урок «Идиома CRTP» бесплатный?

Да — полный текст урока «Идиома CRTP» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C++ Academy, подпишись на CoddyKit PRO. Курс C++ Academy содержит 4 уроков всего.

Чему я научусь в уроке «Идиома CRTP»?

Шаблонный паттерн с рекурсивным использованием типа Ты практикуешь C++ Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C++ Academy?

Предыдущий опыт не требуется. C++ Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Идиома CRTP»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C++ Academy?

Да. Каждый урок C++ Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Идиома CRTP
  2. Статический полиморфизм
  3. Миксины с CRTP
  4. Когда использовать CRTP
← Назад к C++ Academy