CRTP расшифровывается как «Любопытно повторяющийся шаблон шаблона». Это идиома C++, в которой класс определяется как специализация шаблона класса, из которого он получен. Этот шаблон можно использовать для достижения статического полиморфизма или полиморфизма, который разрешается во время компиляции, а не во время выполнения.
Вот пример того, как CRTP можно использовать в C++:
template <typename Derived> class Base { public: void foo() { static_cast<Derived*>(this)->fooImpl(); } }; class Derived : public Base<Derived> { public: void fooImpl() { // implementation of Derived::foo() } }; int main() { Derived d; d.foo(); // Calls Derived::fooImpl() return 0; }
В этом примере класс Derived
является производным от класса Base
в качестве специализации шаблона Base<Derived>
. Метод foo()
в классе Base
действует как диспетчерская функция, которая вызывает реализацию в классе Derived
с помощью оператора static_cast
. В функции main
мы создаем экземпляр класса Derived
, d
, а затем вызываем для него метод foo
. Поскольку Derived
определен как подкласс Base<Derived>
, вызов foo
приведет к реализации foo
в классе Base
. Реализация foo
в классе Base
использует статическое приведение для приведения this
к указателю на Derived
, а затем вызывает метод fooImpl
для этого указателя. Поскольку Derived
предоставил реализацию для fooImpl
, эта реализация будет выполняться, когда foo
вызывается для экземпляра Derived
.
Обратите внимание, что метод foo
определен в классе Base
, а его реализация невелика, поэтому компилятор может выбрать его встраивание, что приведет к еще более быстрому коду.
Используя CRTP, мы можем достичь статического полиморфизма без накладных расходов на виртуальные функции, поскольку отправка правильной реализации разрешается во время компиляции, а не во время выполнения.
Другим примером CRTP является реализация счетчика объектов. Класс счетчика объектов обеспечивает подсчет количества существующих объектов определенного типа. Вот пример:
template <typename T> class ObjectCounter { public: ObjectCounter() {object_count_++;} ObjectCounter(const ObjectCounter&) { object_count_++; } ObjectCounter(ObjectCounter&&) noexcept { object_count_++; } ~ObjectCounter() { object_count_--; } static std::size_t object_count() { return object_count_; } private: static inline std::size_t object_count_{0}; // C++17 }; class MyClass: public ObjectCounter<MyClass> { // MyClass implementation };
В этом примере ObjectCounter
— это базовый класс, обеспечивающий подсчет количества существующих объектов определенного типа. Переменная-член object_count_
определяется как статическая переменная внутри шаблона, а функция object_count()
определяется как статическая функция-член, которая возвращает значение object_count_
.
Производный класс MyClass
определяется как подкласс ObjectCounter<MyClass>
. Это означает, что каждый экземпляр MyClass
будет увеличивать значение object_count_
в своем базовом классе, и каждый раз, когда экземпляр MyClass
уничтожается, он будет уменьшать значение object_count_
.
Вы можете использовать функцию object_count()
, чтобы получить количество существующих объектов типа MyClass
:
int main() { MyClass a, b, c; std::cout << MyClass::object_count() << std::endl; // outputs 3 return 0; }
В этом примере a
, b
и c
являются экземплярами MyClass
, а вызов MyClass::object_count()
выводит значение 3
, указывающее, что существует три экземпляра MyClass
.
Подводя итог, можно сказать, что Curiously Recurring Template Pattern (CRTP) — это мощная идиома C++, которая позволяет создавать классы, связанные посредством наследования и шаблонов. Если вы программист на C++, вам следует рассмотреть возможность использования CRTP.