Идиома 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 — локальная установка не требуется.