Jump to content

Любопытно повторяющийся шаблон шаблона

Любопытно повторяющийся шаблон шаблона ( CRTP ) — это идиома, первоначально разработанная в C++ , в которой класс X класса происходит от создания экземпляра шаблона с использованием X себя как аргумент шаблона. [1] В более общем смысле он известен как F-ограниченный полиморфизм и является формой F -ограниченной квантификации .

Этот метод был формализован в 1989 году как « F -ограниченная количественная оценка». [2] Название «CRTP» было независимо придумано Джимом Коплиеном в 1995 году. [3] кто наблюдал это в одном из самых ранних C++ шаблонов кода а также в примерах кода, которые Тимоти Бадд создал на своем мультипарадигмальном языке Leda. [4] Иногда его называют «перевернутым наследованием». [5] [6] из-за того, что он позволяет расширять иерархию классов путем замены разных базовых классов.

Реализация Microsoft CRTP в библиотеке активных шаблонов (ATL) была независимо обнаружена, также в 1995 году, Яном Фалькиным, который случайно получил базовый класс из производного класса. Кристиан Бомонт впервые увидел код Яна и поначалу подумал, что он не сможет скомпилироваться доступным в то время компилятором Microsoft. После того, как выяснилось, что это действительно работает, Кристиан основал всю конструкцию ATL и библиотеки шаблонов Windows (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>
{
    // ...
};

Некоторыми вариантами использования этого шаблона являются статический полиморфизм и другие методы метапрограммирования, например, описанные Андреем Александреску в книге Modern C++ Design . [7] Он также занимает видное место в реализации парадигмы данных, контекста и взаимодействия на C++ . [8] Кроме того, CRTP используется стандартной библиотекой C++ для реализации std::enable_shared_from_this функциональность. [9]

Статический полиморфизм

[ редактировать ]

Обычно шаблон базового класса использует тот факт, что тела функций-членов (определения) не создаются в течение долгого времени после их объявления, и будет использовать члены производного класса в своих собственных функциях-членах посредством использования приведения ; например:

template <class T> 
struct Base
{
    void interface()
    {
        // ...
        static_cast<T*>(this)->implementation();
        // ...
    }

    static void static_func()
    {
        // ...
        T::static_sub_func();
        // ...
    }
};

struct Derived : public Base<Derived>
{
    void implementation();
    static void static_sub_func();
};

В приведенном выше примере функция Base<Derived>::interface(), хотя и было объявлено до существования struct Derived известно компилятору (т.е. до Derived объявлен), фактически не создается компилятором до тех пор, пока он не будет фактически вызван каким-либо более поздним кодом, который происходит после объявления Derived (не показано в приведенном выше примере), так что во время создания экземпляра функции «интерфейс» объявление Derived::implementation() известно.

Этот метод достигает эффекта, аналогичного использованию виртуальных функций , без затрат (и некоторой гибкости) динамического полиморфизма . Некоторые называют это конкретное использование CRTP «имитируемой динамической привязкой». [10] Этот шаблон широко используется в библиотеках Windows ATL и WTL .

Чтобы уточнить приведенный выше пример, рассмотрим базовый класс без виртуальных функций . Всякий раз, когда базовый класс вызывает другую функцию-член, он всегда вызывает свои собственные функции базового класса. Когда мы наследуем класс от этого базового класса, мы наследуем все переменные-члены и функции-члены, которые не были переопределены (без конструкторов или деструкторов). Если производный класс вызывает унаследованную функцию, которая затем вызывает другую функцию-член, то эта функция никогда не будет вызывать какие-либо производные или переопределенные функции-члены в производном классе.

Однако если функции-члены базового класса используют CRTP для всех вызовов функций-членов, переопределенные функции в производном классе будут выбраны во время компиляции. Это эффективно эмулирует систему вызова виртуальных функций во время компиляции без затрат на размер или накладные расходы на вызов функций ( структуры VTBL и поиск методов, механизм VTBL с множественным наследованием), но не позволяет сделать этот выбор во время выполнения.

Счетчик объектов

[ редактировать ]

Основная цель счетчика объектов — получение статистики создания и уничтожения объектов для данного класса. [11] Это можно легко решить с помощью CRTP:

template <typename T>
struct counter
{
    static inline int objects_created = 0;
    static inline int objects_alive = 0;

    counter()
    {
        ++objects_created;
        ++objects_alive;
    }
    
    counter(const counter&)
    {
        ++objects_created;
        ++objects_alive;
    }
protected:
    ~counter() // objects should never be removed through pointers of this type
    {
        --objects_alive;
    }
};

class X : counter<X>
{
    // ...
};

class Y : counter<Y>
{
    // ...
};

Каждый раз, когда объект класса X создается, конструктор counter<X> вызывается, увеличивая как созданный, так и активный счетчик. Каждый раз, когда объект класса X уничтожается, количество живых уменьшается. Важно отметить, что counter<X> и counter<Y> это два отдельных класса, поэтому они будут вести отдельный подсчет Xпесок Yс. В этом примере CRTP такое различие классов является единственным использованием параметра шаблона ( T в counter<T>) и причину, по которой мы не можем использовать простой базовый класс без шаблонов.

Полиморфная цепочка

[ редактировать ]

Цепочка методов , также известная как идиома именованных параметров, представляет собой общий синтаксис для вызова нескольких вызовов методов в объектно-ориентированных языках программирования. Каждый метод возвращает объект, что позволяет объединить вызовы в одну инструкцию без необходимости использования переменных для хранения промежуточных результатов.

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

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;
    }
};

мы «теряем» конкретный класс, как только вызываем функцию базы:

//                           v----- we have a 'Printer' here, not a 'CoutPrinter'
CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!"); // compile error

Это происходит потому, что «печать» является функцией базы — «Принтер» — и затем возвращает экземпляр «Принтер».

CRTP можно использовать, чтобы избежать такой проблемы и реализовать «полиморфную цепочку»: [12]

// Base class
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;
};
 
// Derived class
class CoutPrinter : public Printer<CoutPrinter>
{
public:
    CoutPrinter() : Printer(cout) {}
 
    CoutPrinter& SetConsoleColor(Color c)
    {
        // ...
        return *this;
    }
};
 
// usage
CoutPrinter().print("Hello ").SetConsoleColor(Color.red).println("Printer!");

Полиморфная копия

[ редактировать ]

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

// Base class has a pure virtual function for cloning
class AbstractShape {
public:
    virtual ~AbstractShape () = default;
    virtual std::unique_ptr<AbstractShape> clone() const = 0;
};

// This CRTP class implements clone() for Derived
template <typename Derived>
class Shape : public AbstractShape {
public:
    std::unique_ptr<AbstractShape> clone() const override {
        return std::make_unique<Derived>(static_cast<Derived const&>(*this));
    }

protected:
   // We make clear Shape class needs to be inherited
   Shape() = default;
   Shape(const Shape&) = default;
   Shape(Shape&&) = default;
};

// Every derived class inherits from CRTP class instead of abstract class

class Square : public Shape<Square>{};

class Circle : public Shape<Circle>{};

Это позволяет получать копии квадратов, кругов или любых других фигур путем shapePtr->clone().

Подводные камни

[ редактировать ]

Одна из проблем статического полиморфизма заключается в том, что без использования общего базового класса, такого как AbstractShape В приведенном выше примере производные классы не могут храниться однородно, то есть помещать разные типы, производные от одного и того же базового класса, в один и тот же контейнер. Например, контейнер, определенный как std::vector<Shape*> не работает, потому что Shape это не класс, а шаблон, требующий специализации. Контейнер, определенный как std::vector<Shape<Circle>*> могу только хранить Circleс, не Squareс. Это связано с тем, что каждый из классов, производных от базового класса CRTP, Shape это уникальный тип. Распространенным решением этой проблемы является наследование от общего базового класса с помощью виртуального деструктора, такого как AbstractShape приведенный выше пример, позволяющий создать std::vector<AbstractShape*>.

Вывод этого

[ редактировать ]

Использование CRTP можно упростить с помощью функции C++23 , выяснив это. [13] [14] Для функции signature_dish вызвать производную функцию-член cook_signature_dish, ChefBase должен быть шаблонного типа и CafeChef необходимо наследовать от ChefBase, передав его тип в качестве параметра шаблона.

template <typename T>
struct ChefBase
{
    void signature_dish()
    {
        static_cast<T*>(this)->cook_signature_dish();
    }
};

struct CafeChef : ChefBase<CafeChef>
{
    void cook_signature_dish() {}
};

Если используется явный параметр объекта, ChefBase не требует шаблонизации и CafeChef может быть получено из ChefBase ясно. Поскольку self параметр автоматически выводится как правильный производный тип, приведение не требуется.

struct ChefBase
{
    template <typename Self>
    void signature_dish(this Self&& self)
    {
        self.cook_signature_dish();
    }
};

struct CafeChef : ChefBase
{
    void cook_signature_dish() {}
};

См. также

[ редактировать ]
  1. ^ Абрахамс, Дэвид ; Гуртовой, Алексей (январь 2005 г.). Метапрограммирование шаблонов C++: концепции, инструменты и методы из Boost и за его пределами . Аддисон-Уэсли. ISBN  0-321-22725-5 .
  2. ^ Уильям Кук; и др. (1989). «F-ограниченный полиморфизм для объектно-ориентированного программирования» (PDF) .
  3. ^ Коплин, Джеймс О. (февраль 1995 г.). «Любопытно повторяющиеся шаблоны шаблонов» (PDF) . Отчет по C++ : 24–27.
  4. ^ Бадд, Тимоти (1994). Мультипарадигмальное программирование в Leda . Аддисон-Уэсли. ISBN  0-201-82080-3 .
  5. ^ «Кафе отступников: ATL и перевернутое наследование» . 15 марта 2006 г. Архивировано из оригинала 15 марта 2006 г. Проверено 9 октября 2016 г. {{cite web}}: CS1 maint: bot: исходный статус URL неизвестен ( ссылка )
  6. ^ «ATL и перевернутое наследование» . 4 июня 2003 г. Архивировано из оригинала 4 июня 2003 г. Проверено 9 октября 2016 г. {{cite web}}: CS1 maint: bot: исходный статус URL неизвестен ( ссылка )
  7. ^ Александреску, Андрей (2001). Современный дизайн на C++: применение общих шаблонов программирования и проектирования . Аддисон-Уэсли. ISBN  0-201-70431-5 .
  8. ^ Коплин, Джеймс ; Бьёрнвиг, Гертруда (2010). Бережливая архитектура: для гибкой разработки программного обеспечения . Уайли. ISBN  978-0-470-68420-7 .
  9. ^ "std::enable_shared_from_this" . Проверено 22 ноября 2022 г.
  10. ^ «Имитация динамического связывания» . 7 мая 2003 года. Архивировано из оригинала 9 февраля 2012 года . Проверено 13 января 2012 г.
  11. ^ Мейерс, Скотт (апрель 1998 г.). «Подсчет объектов в C++» . Журнал пользователей C/C++ .
  12. ^ Арена, Марко (29 апреля 2012 г.). «Используйте CRTP для полиморфной цепочки» . Проверено 15 марта 2017 г.
  13. ^ Гашпер Ажман; Сай Брэнд; Бен Дин; Барри Ревзин (12 июля 2021 г.). «Вывод этого» .
  14. ^ «Явный параметр объекта» . Проверено 27 декабря 2023 г.
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 32f43d06a88facb9814a6f696b8af707__1715256660
URL1:https://arc.ask3.ru/arc/aa/32/07/32f43d06a88facb9814a6f696b8af707.html
Заголовок, (Title) документа по адресу, URL1:
Curiously recurring template pattern - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)