На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> Юзкейсы для CRTP
    Да, речь идет о CRTP (Странно рекурсивный шаблон). Так, для напоминания:

    ExpandedWrap disabled
      template<class T>
      class Base
      {
          // ...
      };
      class Derived : public Base<Derived>
      {
          // ...
      };

    Обычно говорят, что этот подход позволяет реализовать идиому статического полиморфизма.
    Ну ок, пусть так. Но хотелось бы примеры сабжа, где это действительно дает выхлоп.
    Например, для проектирования либы для GUI тут ни разу не катит, а где катит?
      Цитата Majestio @
      Обычно говорят, что этот подход позволяет реализовать идиому статического полиморфизма.
      Строго говоря, нет. Статический полиморфизм прекрасно работает и без него, достаточно просто заюзать требуемое свойство типа. Например:
      ExpandedWrap disabled
        #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 он означает дерево иерархии, в котором полиморфная связь между классами перевёрнута "вверх ногами". Если в классическом полиморфизме между полиморфными классами базовые классы декларируют интерфейсы, которые в дальнейшем и используются, а реализуются они в производных классах, то при статическом всё наоборот: декларирующие интерфейс классы являются самыми производными, а реализуют эти интерфейсы их базовые классы. Это имеет тот профит, что нет необходимости в позднем связывании, т.к. у производных классов уже есть статические связи с унаследованными от базовых классов методами.
        Цитата Qraizer @
        Если в классическом полиморфизме между полиморфными классами базовые классы декларируют интерфейсы, которые в дальнейшем и используются, а реализуются они в производных классах, то при статическом всё наоборот: декларирующие интерфейс классы являются самыми производными, а реализуют эти интерфейсы их базовые классы. Это имеет тот профит, что нет необходимости в позднем связывании, т.к. у производных классов уже есть статические связи с унаследованными от базовых классов методами.

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

          Добавлено
          Вот к примеру классический динамический полиморфизм:
          ExpandedWrap disabled
            #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);
            }
          А вот для сравнения он же, но статический
          ExpandedWrap disabled
            #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 особо полезен в связке динамики и статики, т.е. когда и производные классы наследуют функционал базовых, и базовые получают явные ссылки на производные от них классы.
            Цитата Qraizer @
            ATL, к примеру, вся сплошь на шаблонах.

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

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

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

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

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


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0479 ]   [ 16 queries used ]   [ Generated: 9.12.22, 22:44 GMT ]