Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > C/C++: Общие вопросы > Юзкейсы для CRTP


Автор: Majestio 01.06.22, 10:46
Да, речь идет о CRTP (Странно рекурсивный шаблон). Так, для напоминания:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    template<class T>
    class Base
    {
        // ...
    };
    class Derived : public Base<Derived>
    {
        // ...
    };

Обычно говорят, что этот подход позволяет реализовать идиому статического полиморфизма.
Ну ок, пусть так. Но хотелось бы примеры сабжа, где это действительно дает выхлоп.
Например, для проектирования либы для GUI тут ни разу не катит, а где катит?

Автор: Qraizer 01.06.22, 13:19
Цитата Majestio @
Обычно говорят, что этот подход позволяет реализовать идиому статического полиморфизма.
Строго говоря, нет. Статический полиморфизм прекрасно работает и без него, достаточно просто заюзать требуемое свойство типа. Например:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include <vector>
    #include <list>
     
    template <typename T>
    class StaticPoly
    {
      T t;
     
    public:
      void resize(int x) { t.resize(x); }
    };
     
    int main()
    {
      StaticPoly<std::vector<int>>                sp1;
      StaticPoly<std::list<std::vector<float>>>   sp2;
     
      sp1.resize(123);
      sp2.resize(123);
    }
Вообще, понятия статического и динамического полиморфизма не являются академическими, они введены сообществом C++ во времена, когда (внезапно :D ) выяснилось, что шаблоны тоже отвечают принципам полиморфного поведения. Академически есть просто полиморфизм, и он далее классифицируется по методам его реализации, а не признакам и свойствам. Но это так, к слову.
Из-за неакадемичности термина "статический полиморфизм" бывает, что его смысл в разных контекстах не вполне правильно понимается, т.к. у него нет единого формального определения. Конкретно в контексте CRTP он означает дерево иерархии, в котором полиморфная связь между классами перевёрнута "вверх ногами". Если в классическом полиморфизме между полиморфными классами базовые классы декларируют интерфейсы, которые в дальнейшем и используются, а реализуются они в производных классах, то при статическом всё наоборот: декларирующие интерфейс классы являются самыми производными, а реализуют эти интерфейсы их базовые классы. Это имеет тот профит, что нет необходимости в позднем связывании, т.к. у производных классов уже есть статические связи с унаследованными от базовых классов методами.

Автор: Majestio 01.06.22, 16:34
Цитата Qraizer @
Если в классическом полиморфизме между полиморфными классами базовые классы декларируют интерфейсы, которые в дальнейшем и используются, а реализуются они в производных классах, то при статическом всё наоборот: декларирующие интерфейс классы являются самыми производными, а реализуют эти интерфейсы их базовые классы. Это имеет тот профит, что нет необходимости в позднем связывании, т.к. у производных классов уже есть статические связи с унаследованными от базовых классов методами.

Ну да все верно. Но сабж остается открытым.

Автор: Qraizer 01.06.22, 16:40
Цитата Majestio @
Но хотелось бы примеры сабжа, где это действительно дает выхлоп.
Та ну откуда ж я знаю-то. Я использовал один раз в своей реализации мультиметодов, но это весьма специфическая задача. Я дал общее представление об архитектуре статически полиморфного дерева и описал основой профит, а где это может пригодиться, тут решать каждому самостоятельно.
Цитата Majestio @
Например, для проектирования либы для GUI тут ни разу не катит, а где катит?
Не факт. ATL, к примеру, вся сплошь на шаблонах. Вполне себе пример. Всё ж зависит от спроектированной тобой архитектуры. Ты вот считаешь, что не катит, потому что не представляешь себе даже примерное виденье иерархической архитектуры, но это не значит, что её не может существовать.

Добавлено
Вот к примеру классический динамический полиморфизм:
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include <iostream>
     
    class DynaPolyBase
    {
    public:
      virtual void f() const = 0;
    };
     
    class DynaPolyDerive1: public DynaPolyBase
    {
    public:
      void f() const { std::cout << "I'm DynaPolyDerive1" << std::endl; }
    };
     
    class DynaPolyDerive2: public DynaPolyBase
    {
    public:
      void f() const { std::cout << "I'm DynaPolyDerive2" << std::endl; }
    };
     
    void check(const DynaPolyBase& base)
    {
      base.f();
    }
     
    int main()
    {
      DynaPolyDerive1 dp11;
      DynaPolyDerive2 dp12;
     
      check(dp11);
      check(dp12);
    }
А вот для сравнения он же, но статический
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    #include <iostream>
     
    template <typename D>
    class StatPolyBase
    {
    public:
      void f() const { static_cast<const D*>(this)->f(); }
    };
     
    class StatPolyDerive1: public StatPolyBase<StatPolyDerive1>
    {
    public:
      void f() const { std::cout << "I'm StatPolyDerive1" << std::endl; }
    };
     
    class StatPolyDerive2: public StatPolyBase<StatPolyDerive2>
    {
    public:
      void f() const { std::cout << "I'm StatPolyDerive2" << std::endl; }
    };
     
    template <typename C>
    void check(const StatPolyBase<C>& interface)
    {
      interface.f();
    }
     
    int main()
    {
      StatPolyDerive1 sp11;
      StatPolyDerive2 sp12;
     
      check(sp11);
      check(sp12);
    }
Тут check(), чтобы сохранить полиморфное поведение, тоже должна быть полиморфной и при этом статически, т.е. в данном случае шаблонной.

Добавлено
P.S. Замечу, что как и говорил в начале, базовый класс у StatPolyDeriveX не требуется, достаточно StatPolyBase иметь шаблонный параметр D. CRTP особо полезен в связке динамики и статики, т.е. когда и производные классы наследуют функционал базовых, и базовые получают явные ссылки на производные от них классы.

Автор: Majestio 01.06.22, 18:03
Цитата Qraizer @
ATL, к примеру, вся сплошь на шаблонах.

Вот тут я к примеру не понимаю - а как в рантайме создать элемент, если шаблоны они только при компиляции?

Автор: Qraizer 01.06.22, 18:13
А никак. Статика на то и статика. Если тебе нужно в ран-тайме подменять реализации, то статический полиморфизм тебе по определению не подходит. Но вот подумай, а насколько часто ты сталкиваешься с задачей, где подменять поведение нужно в динамике? С GUI-ём это довольно редко происходит, например.

Автор: Majestio 01.06.22, 18:24
Цитата Qraizer @
С GUI-ём это довольно редко происходит, например.

Вот тут ты ошибаешься про GUI. Например в Qt с помощью UI можно делать только примитивные интерфейсы. Но если нужно делать что-то более гибкое, к примеру по условию добавить поле, или заменить один контрол на группу в зависимости от условий - только динамическим полиморфизмом это можно реализовать. И это бывает очень часто. Хотя UI из Qt - это просто сахарок от дизайнера форм, но используются потом все те же типы с динамическим полиморфизмом.

Автор: Qraizer 01.06.22, 20:26
Ты ошибаешься. Я только один раз за всю сознательную жизнь делал интерфейс динамическим, потому что пришлось. В остальные у меня всё было готово в статике. Всё нужное нарисовал, засунул в ресурсы, и грузил нужное в зависимости от потребностей.
Qt наследует идиому web-а, которая по своей природе динамическая. Естественно в статику это не переведёшь. Однако возьми любое приложение на нативном WinAPI, и внезапно понимаешь, что у тебя просто нет подходящих библиотек,а самому писать лень.

Автор: Majestio 02.06.22, 08:38
Цитата Qraizer @
Я только один раз за всю сознательную жизнь делал интерфейс динамическим, потому что пришлось. В остальные у меня всё было готово в статике.

Ну я не могу это комментировать, т.к. не знаю твоих задач. В своих задачах заранее предопределенных форм мне не хватало, как правило. Поэтому я плюнул на все эти формо-ресурсы и весь интерфейс всегда строю программно.

Автор: Qraizer 02.06.22, 13:24
Цитата Majestio @
Ну я не могу это комментировать, т.к. не знаю твоих задач.
Обычные десктопные приложения.
Цитата Majestio @
Поэтому я плюнул на все эти формо-ресурсы и весь интерфейс всегда строю программно.
Значит тебе статика действительно не подходит. В той задаче, где мне нужна была динамика, речь шла о библиотечной функции, поэтому я не мог использовать ресурсы, т.к. в .lib их не засунешь. В остальных случаях обычного редактора ресурсов было за глаза, это и мне удобнее, и пользователю, у него глаза не разбегаются по живому интерфейсу.

Добавлено
Что за CRTP, то из примеров я могу привести только свой. Основная причина необходимости в нём уже указана: когда тебе нужна двунаправленная связь по иерархии – как сверху вниз, так и снизу вверх. В этом случае сверху вниз через обычный полиморфный вызов виртуальных методов, а снизу вверх – через шаблонный параметр CRTP. Это позволяет избежать медленных dynamic_cast<> и ненадёжных в плане того, что проверки будут в runtime. CRTP работает быстро, а корректность чекается в compile time.
В моём примере я строил линейную иерархию классов по списку типов, который передаётся извне пользователем и естественно неизвестен на стадии разработки библиотеки, но известен на момент компиляции. Эта иерархия состояла из классов, являвшихся шаблонными и конкретизированные каждый соответствующим типом из переданного списка. Они в итоге получались унаследованными друг от друга. Каждый такой класс имеет метод, и они все по сути одинаковые, но обрабатывающие данные по-разному, т.к. этим управляет каждый конкретный класс в соответствие со своим типом. И идея была в том, что самый базовый класс в иерархии хранит массив указателей на эти методы и индексирует нужный посредством индексации нужного типа из переданного списка. Ничего сложного, указатели на методы всё решают, но засада была в том, что каждый метод имеет разный тип, т.к. лежит в своём классе, поэтому у них у всех была разная квалификация DerivedClass:: и соответственно каждому должен был приходить уникальный this, который свой собственный. Проблема была решена созданием самого производного класса, к указателю на методы которого все частные указатели и приводились, и то же делалось с this. В итоге самый базовый хранил указатели в едином пространстве MostDerivedClass:: и this тоже указывал на MostDerivedClass, а когда он вызывал конкретный метод из массива, компилятор сам делал нужные неявные восходящие касты и this, и поинтера. Но для этого самому базовому нужно было знать о самом производном, и вот он как раз до него спускался через CRTP.

Автор: Majestio 02.06.22, 14:09
Нормуль, спасибо за пример.

Автор: Qraizer 02.06.22, 14:18
Забыл ещё уточнить. Контейнер методов и this лежали в самом базовом, т.к. каждому производному к ним нужен доступ, и они его получали простым наследованием. Иначе б их можно было хранить в самом производном, и не понадобился бы никакой CRTP. Ну т.е. получилась та самая двунаправленная связь во всей красе.

Добавлено
Вообще, полный код есть на githab-е, конкретно в варианте RTTI, но ...как бы так помягче... там полный капут. Около 1000 строк метакода, шаблоны местами в 8-9 параметров, половина из них шаблоны шаблонов... Мультиметоды, они такие. Зато я могу похвастаться, что это самая производительная (Visitor практически так же производителен, как и нативная реалиазция, если б она в языке была) и практически не имеющая ограничений по использованию. Т.е. использовать мультиметоды можно практически так же, как мы привыкли использовать обычные.
У нас на форуме я тут создавал три (вроде бы) темы с их описанием. Можешь поискать, архитектура расписана довольно подробно. В описании на githab есть ссылки, если что.

Автор: Majestio 02.06.22, 14:30
Спасибо

Автор: Majestio 06.06.22, 19:41
Qraizer. немного прокачал я вопрос. Очень много народу говорит, что профит от CRTP ничтожен по сравнению с геммором от чтения исходников с этой шляпой и последующей разработкой/доработкой. От себя ... и возможно они правы. Если хочешь максимум быстродействия и минимум расходных ресурсов - бери чистый Си. Там реально все под контролем (ни или почти).

Автор: Qraizer 06.06.22, 21:33
Цитата Majestio @
Очень много народу говорит, что профит от CRTP ничтожен по сравнению с геммором от чтения исходников с этой шляпой и последующей разработкой/доработкой.
Опять же зависит от того, что в него вкладывают. Если речь о статическом полиморфизме как таковом, а ведь вроде с этого тезиса началась тема, то никто бы его тогда не использовал, однако осмотрись вокруг, его полно, и в последние лет 10 его используют чуть ли не чаще обычного динамического. Почитай, любой шаблонный код суть статический полиморфизм, и никакого геморра как-то и близко не наблюдается. Если же речь конкретно о CRTP, то я сразу говорил, что ниша, где он действительно нужен, довольно узка. Так что ничего необычного в выводах не вижу. Если пытаться натянуть сову на глобус, будет больно не только сове.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)