Curiously recurring template pattern
Curiously Recurring Template Pattern (CRTP) идиома языка C++, название которой можно примерно перевести как Любопытный рекурсивный шаблон или Любопытный повторяющийся шаблон, часто просто Рекурсивный шаблон, состоящая в том, что некоторый класс X
является наследником от шаблона класса, использующего X
как шаблонный параметр.
Используется и в Java — например, любой enum X
является наследником от Enum<X>
.
История
[править | править код]Эта техника была формализована в 1989 году под названием «F-bounded quantification». Название CRTP было предложено независимо Джеймсом Коплиеном[англ.] в 1995 году. Он обнаружил эту идиому в ранних шаблонных кодах на C++, а также в примерах, которые Тимоти Бадд[англ.] сделал для своего мультипарадигменного языка Leda[англ.]. Эта идиома иногда называется «Upside-Down Inheritance» (перевёрнутое наследование) из-за того, что она позволяет расширять иерархию классов за счёт подстановки других базовых классов.
Реализация от Microsoft в ATL была открыта независимо Яном Фалкином (англ. Jan Falkin) также в 1995 году. Он случайно унаследовал базовый класс от класса наследника. Кристиан Бомон (англ. Christian Beaumont), заметив этот код, решил, что он не может быть скомпилирован, но, выяснив, что может, решил положить эту ошибку в основу ATL и WTL.
Основная форма
[править | править код]// The Curiously Recurring Template Pattern (CRTP)
template<class T>
class Base
{
// methods within Base can use template to access members of Derived
};
class Derived : public Base<Derived>
{
// ...
};
Статический полиморфизм
[править | править код]template <class T>
struct Base
{
void interface()
{
// ...
static_cast<T*>(this)->implementation();
// ...
}
static void static_func()
{
// ...
T::static_sub_func();
// ...
}
};
struct Derived : Base<Derived>
{
void implementation();
static void static_sub_func();
};
В примере выше функция Base<Derived>::interface()
известна компилятору, хотя и объявлена перед объявлением структуры struct Derived
. Тем не менее, эта функция не инстанцируется до момента фактического вызова, который должен произойти после объявления Derived
.
Это приём позволяет достичь эффекта, похожего на использование виртуальных функций без использования динамического полиморфизма. В этом смысле эта идиома используется в библиотеках Windows ATL и WTL.
Развивая предыдущий пример, предположим, что есть базовый класс вообще без виртуальных методов. Это значит, что вызов любого метода базового класса, используя указатель на базовый класс, приведёт к вызову метода именно этого базового класса, даже если он был переопределён в классе-наследнике. Например, если наследник вызывает непереопределенный метод базового класса, который, в свою очередь, вызывает другой метод базового класса, уже переопределённый наследником, то будет вызван не метод наследника, а метод базового класса.
Однако, если методы базового класса используют CRTP для вызовов других методов, то переопределённые функции будут выбраны во время компиляции. Это эффективно эмулирует систему виртуальных функций во время компиляции без необходимости платить цену за динамический полиморфизм (таблицы виртуальных методов, время, затрачиваемое на выбор метода, множественное наследование), но не позволяет делать этого во время выполнения.
Литература
[править | править код]- David Abrahams, Aleksey Gurtovoy. C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond. — Addison-Wesley, 2005. — ISBN 0-321-22725-5.