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

    Только что закончил читать хорошую книжку по паттернам проектирования "Приемы объектно-ориентированного проектирования / паттерны проектирования". Э.Гамма, Р.Хелм, Р.Джексон, Дж.Влиссиденс, 2001г.

    Книженция крайне занимательная. Но возник вопрос, "как эмулировать в языке программирования понятие mixin без явной языковой поддержки?"
    Кому не лениво, прошу привести примеры. В примерах нужно отразить:

    1) Реализацию
    2) Альтернативный пример, где тот же вопрос решается ... но гораздо ущербнее без сабжа
    3) Набор типовых примеров, где это привносит профит не в ущерб производительности

    Спасибо эдванс!!!
      Примеси, я использую в немного урезанном виде. Там где они дают профит, лично для меня.
      Вообще, в C++ их фактически приходится эмулировать.

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

      ExpandedWrap disabled
        class value_mixed
        {
        public:
        typedef value_type;
         
        value_type get_value()const
        {
        return m_value;
        }
         
        void set_value( const value_type& c_value )
        {
        m_value = c_value;
        }
         
        void set_value( value-type&& r_value )
        {
        m_value = move( r_value );
        }
         
        protected:
         
        value_mixed(const value_type& c_value ):
        m_value( c_value )
        {
        }
         
        ~value_mixed()
        {
        }
         
        private:
         
        value_type m_value;
         
        };


      Иногда за вроде бы такими простыми вещами, когда надо просто хранить и предоставлять доступ к значению, тянутся всякие дополнительные фишечки. Например, несколько вариантов для получения и задания значения. Конструкторы копирования и переноса, а так же соответствующие операторы.
      А набивать повторяющийся код во-второй, третий и десятый раз уже задалбывает! Проще сделать отдельный клас, в который вынести некий повторяющийся функционал.
      Подключается всё так же, как обычный базовый класс. Тем более по сути, это и есть самый обыкновенный базовый класс.

      Гым. Может возникнуть вопрос, почему я это называю примесью?
      Есть правило, что наследники базового класса, должны сохранять суть своего предка. Это философия понятности кода.
      Но когда предок, делает какюу-то плюшку,
      некую плюшку, а у потомка вообще куча своих посторонних дел, то философия ломается.
      Чтобы сделать как хочется и обойти филосовскую проблему, просто называем примесью обычный базовый класс.

      Касательно производительности машинного исполнения, то просадки не будет или же она будет минимальной из-за промаха кэша.
      Весь профит в упрощении программирования, уменьшении копипасты кода.

      Но есть более хардкорные примиси. Я их обычно не использую, из-за неоднозначности реализации и даже из-за просадки производительности. Впрочем, бывают весьма интересные случаи.

      следующий шаблон может использоваться для добавления в класс оператора «!=» при наличии оператора «==»:

      ExpandedWrap disabled
         template <typename T> struct AddNoEq {
            virtual bool operator==(const T &cmp) const = 0;
            bool operator!=(const T &cmp) const {
                return !static_cast<const T*>(this)->operator== (cmp);
            }
         };



      Простой пример использования для класса комплексных чисел:

      ExpandedWrap disabled
         #include <iostream>
         
         struct Complex : public AddNoEq<Complex> {
            Complex(int re, int im): re_(re), im_(im) { }
         
            virtual bool operator==(const Complex& cmp) const {
                return cmp.re_ == this->re_ && cmp.im_ == this->im_;
            }
            // ...
         private:
            int re_, im_;
         };
         
         int main()
         {
            Complex a(1, 2), b(2, 3);
         
             if (a != b)
                std::cout << "Так и должно быть" << std::endl;
         
             return 0;
         }



      Данный метод в более развёрнутом виде используется в библиотеке «Boost operators».

      Это я с википедии упёр. Если пошаманить, то можно заоптимизировать, отказавшись от виртуализации, в замен на приведение указателя базового класса, на класс потомка, в базовом классе.

      Плюшки примиси в том, что какой-то код, пишется один раз и множество раз используется. Альтернативой будет многократное набивание одного и того же кода.
        Цитата JoeUser @
        1) Реализацию
        2) Альтернативный пример, где тот же вопрос решается ... но гораздо ущербнее без сабжа
        3) Набор типовых примеров, где это привносит профит не в ущерб производительности
        1. Любой набор шаблонных функций. Теоретически, может понадобиться наобъявлять их друзьями нашим классам, однако в подавляющем большинстве случаев, такой список будет невелик и невсегда.
        2. Я не вижу профита от примесей вообще. Отсутствие инкапсулированых в класс определений его поведения считаю бо́льшим недостатком, нежели профит от уменьшения связности. Уменьшать связность при определении поведения класса не требуется, его методы с его атрибутами всегда связаны сильно.
        3. Единственное – и рекомендованное к применению также и по другим причинам – исключение из вышесказанного заключается в замене класса на пространство имён, в которое всё и инкапсулируется. Там могут быть и вспомогательные классы, и свободные, не обязательно шаблонные, функции, и параметры-переменные, и атрибуты-переменные, и всё что хочешь связать с "классом". При этом ADL прекрасно разгребает области видимости, так что явных квалификаций имён "класса" почти нигде не требуется.
          Eric-S и Qraizer, пасип, большое! Осознал!
          Хочу дождаться ответа и от applegame.
            По мне так в плюсах классические миксины не особо нужны, так как аналогичный функционал можно получить путем наследования в том числе и множественного.
            В D я очень редко пользуюсь шаблонными миксинами (строковые миксины - это несколько другое) и во всех таких случаях можно организовать аналогичный функционал при помощи множественного наследования, если бы оно было.
              Цитата applegame @
              По мне так в плюсах классические миксины не особо нужны

              Классические примеси... У меня есть мнение. Не настаиваю на его истинности. Но думаю, что было бы удобно.

              Да, верно, большую часть примесей, можно эмулировать наследованием. И огромный респект, за множественное и виртуальное наследование. Без них было бы сложно проворачивать некоторые трюки.


              Но я сталкивался с моментами, когда приходилось делать дополнительную работу, которую в других языках, делают эти самые примеси.

              Насколько я понимаю, классическая примись, для компилятора, фактически дополняет класс. То есть примись может переопределять виртуальные функции. Но в C++ примись эмулируется базовым классом. А второй базовый класс не может переопределить функции первого базового класса.

              ExpandedWrap disabled
                class worker:
                public i_worker,
                protected  foo_mixed
                {
                public:
                 
                    // переопределение  абстрактной  функции из i_worker
                    void foo() override
                    {
                 
                        // фактически она реализована в эмуляторе примиси, но поскольку не видна, то её приходится вызывать руками
                        return  foo_mixed::foo();
                    }
                 
                };


              Теряется сахар. Приходится многое добавлять руками. Образуются классические проблемы с множественным и виртуальным наследованием... Попытки как-то разрулить ситуацию, вынуждают отказатся от эмуляции паттерна "примесь".

              И после такого, возникает вопрос, а не проще ли в этом случае использовать вложенный объект? Всё равно придётся писать руками проксирующие обёртки.

              Вообщем, мне бы хотелось в C++ настоящие примеси. А то, что есть сейчас, не даёт максимального профита.
                Можно ли утверждать, что паттерн проектирования Decorator полностью реализует функционал классических примесей? Или это все же разные вещи.
                  Цитата JoeUser @
                  Можно ли утверждать, что паттерн проектирования Decorator полностью реализует функционал классических примесей? Или это все же разные вещи.

                  Вопрос поставлен некорректно.

                  Примесь и декоратор, в конце концов, позволяют сделать объект с дополнительным функционалом.
                  Но примесь расширяет функционал изначального класса.
                  А декоратор, расширяет функционал объекта изначального класса.

                  И тут уже надо смотреть на желаемый результат.
                  В одном случае, только примесь. Во втором случае только декоратор. В третьем и то и другое.
                    По сути декоратор "умеет" все то, что и примесь, плюс может еще в рантайме делать еще что-то? Не?
                      Цитата JoeUser @
                      По сути декоратор "умеет" все то, что и примесь,
                      Ага, только с оверхедом.
                        Цитата applegame @
                        с оверхедом

                        А в чем оверхед и как его оценить в %?
                          Цитата JoeUser @
                          А в чем оверхед и как его оценить в %?
                          Ну хотя бы в тмо, что декоратор нужно вешать на уже созданный объект, а в случае примеси объекты сами по себе создаются уже "декорированными". Процент, по очевидным причинам, не оценить никак. Тут действует принцип не заниматься преждевременной пессимизацией. Если нужно чтобы все объекты данного типа были декорированы, то имеет смысл задействовать примеси или наследование, а не декоратор.
                          Сообщение отредактировано: applegame -
                            Ну в принципе да. Одно на этапе компиляции, другое на этапе исполнения.

                            Хотя ... Если взять пример примесей из JavaScript (если я не ошибаюсь ECMAScript 5-й версии):

                            ExpandedWrap disabled
                              // примесь
                              var sayHiMixin = {
                                sayHi: function() {
                                  alert("Привет " + this.name);
                                },
                                sayBye: function() {
                                  alert("Пока " + this.name);
                                }
                              };
                               
                              // использование:
                              function User(name) {
                                this.name = name;
                              }
                               
                              // передать методы примеси
                              for(var key in sayHiMixin) User.prototype[key] = sayHiMixin[key];
                               
                              // User "умеет" sayHi
                              new User("Вася").sayHi(); // Привет Вася


                            Примесь подмешивается не декларативно, а в рантайме исполняемым кодом.
                              Цитата JoeUser @
                              Хотя ... Если взять пример примесей из JavaScript (если я не ошибаюсь ECMAScript 5-й версии):
                              Это такая иммитация примесей. Фактически это декорирование прототипа. Но даже в этом случае все объекты User будут создаваться уже "декорированными", а в случае декоратора кроме создания придется применять "лишний" вызов функции для декорирования свежесозданного объекта.
                                Согласен.
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0483 ]   [ 17 queries used ]   [ Generated: 29.03.24, 09:36 GMT ]