Название: Паттерны Variant и Visitor. Тонкости реализации Отправлено: m_ax от Февраль 23, 2015, 12:55 Паттерны Variant и Visitor. Тонкости реализации
Abstract & Introduction Написание данной заметки было мотивировано недавней темой http://www.prog.org.ru/topic_28407_0.html (http://www.prog.org.ru/topic_28407_0.html) от товарища navrovsky в которой он предложил свою реализацию Variant - аналога boost::any http://www.boost.org/doc/libs/1_57_0/doc/html/any.html (http://www.boost.org/doc/libs/1_57_0/doc/html/any.html). Там же мной была приведена простая реализация аналога boost::variant http://www.boost.org/doc/libs/1_51_0/doc/html/variant.html (http://www.boost.org/doc/libs/1_51_0/doc/html/variant.html). В последствии, русло дискуссии ушло в сторону обсуждения механизмов визитирования коллекции из объектов класса variant и, в частности, патерна Visitor, реализующий этот самый механизм. Однако, у меня осталось стойкое ощущение, что один из участников дискуссии (не будем показывать пальцем), будучи адептом философии: "лучше банальный if-else-switch, понятный и доступный каждому, чем заумные паттерны проектирования, boostы и иже с ними" так и не осознал те преимущества, что даёт связка boost::variant и boost::static_visitor в задачах связанных с операциями над коллекциями из гетерогенных объектов (в данном случае variant'ов). Это ещё одна из причин, что заставила меня написать эту заметку. Ну и наконец, во многом благодаря выше обозначенной теме и процессу обсуждения в ней, а также раскуриванию исходников boost'а, с мыслью "истина где то рядом", пришло осознание как же на самом деле это реализуется в boost'е (как работает boost::static_visitor с boost::variant: это, на самом деле, не так тривиально). И об этом пойдёт речь дальше. И так, здесь я постараюсь показать на простом примере, как изнутри устроен механизм визитирования в boost::variant + boost::static_visitor. Сразу оговорюсь, речь будет идти именно о бустовской реализации: обычный классический вариант паттерна Visitor не столь интересен с академической точки зрения - там всё прозрачно и понятно (почитать о нём можно, например здесь http://cpp-reference.ru/patterns/behavioral-patterns/visitor/ (http://cpp-reference.ru/patterns/behavioral-patterns/visitor/)). Theoretical background Начнём с того, что наша реализация variant'а полностью завязана на меташаблонном программировании. Вот так выглядит объявление класса variant Код
Это обычный variadic template, доступный со стандарта c++11. Другими словами при объявлении variant'а мы должны передать ему (в качестве параметров шаблонов) список всех типов с которым он может работать: Код
Для того, чтоб продвинуться дальше, нам необходимо реализовать несколько вспомогательных "функций", которые могли бы извлекать полезную для нас информацию из списка параметров шаблона <class...Args>. Одна из таких функций - это подсчёт числа всех типов: num_args. Ниже пример её возможной реализации: Код
Вообще, когда мы имеем дело с меташаблонным программированием, это практически всегда связано со специализацией шаблонов (почитать об этом можно, например, здесь http://habrahabr.ru/post/54762/ (http://habrahabr.ru/post/54762/)). Так, специализация Код останавливает рекурсию. Далее, нам бы хотелось иметь возможность приписать каждому типу (из списка) свой уникальный id. Один из вариантов такого индексирования напрашивается сам-собой: id каждого типа в списке соответствует его порядковому номеру (начиная с нуля), т.е. Код Реализовать это можно так: Код
На самом деле здесь ничего сложного нет, нужно только привыкнуть к особенностям меташаблонной магии.. Теперь, когда у нас есть возможность ставить каждому типу из списка свой уникальный id, можно продолжить реализовывать variant: Код
Помимо самих данных _holder знает текущий id типа, объектом которого он владеет. Например, одна из таких функций где это используется: проверка типа: Код
Я не буду здесь останавливаться на описании всех функций, что входят в variant - это не так принципиально (полный код приаттачен к теме). И на этом можно было бы и закончить, но мы хотим иметь возможность элегантного визитирования, так как это реализовано в boost'е.. Но для этого нам необходимо получать тип параметра шаблона по его id. Тип получить можно так (упрощённая версия): Код
Здесь мы опять имеем дело со специализацией type_for_id<0, T, Args...> . Теперь мы можем делать так: Код
Последний штрих: нам осталось написать приватный метод apply_visitor: Код Здесь стоит обратить внимание на строчку: Код Это вспомогательный макрос который подаётся в макрос STATIC_SWITCH Код который в итоге разворачивается в следующее: Код Максимальное значение для макроса STATIC_SWITCH = 256. Это означает, что visitor будет работать с variant'ами у которых число шаблонных параметров не превышает 256. В бусте использован аналогичный подход.. Осталось написать внешнюю (дружественную) функцию apply_visitor: Код
Всё, мы закончили) Теперь чтоб создать своего визитёра, нам нужно отнаследоваться от static_visitor и определить необходимые операторы: Код
Мы добились полной аналогии с boost аналогом. Ещё раз отмечу, что паттерн static_visitor, позволяет легко написать тот самый вожделенный variant_cast: Код И это несомненно лучше бесконечной if-else простыни. Conclusions В заключении отмечу, что концепт связки variant + static_visitor основывается на возможности (в компил тайм) получать id типа и сопряжённой функции: вытаскивать тип по id. Примечательно, что всё это можно реализовать в компил тайме). По сути, в функции apply_visitor используется банальный switch, но его удаётся элегантно скрыть и он, в общем то, не вызывает никаких неприятностей. Несомненным плюсом этого паттерна static_visitor - является полное разделение данных от операций над ними (данные ничего не знают о визитёре, а визитёр может быть применён к вариантам с различными списками типов). Более того, в отличии от классической реализации, здесь не задействован полиморфизм, а это тоже плюс к производительности. Ну и наконец, не могу не сказать, что не раз говорил: boost - это просто кладесь архитектурных решений и их реализаций. Не бойтесь его, пользуйтесь им) Вы только выиграете) PS Исходники с проектом прилагаются.. Название: Re: Паттерны Variant и Visitor. Тонкости реализации Отправлено: Igors от Февраль 23, 2015, 16:53 Ну "адепт" пока "ни асилил" :) Не, ну чижело, правда. Ладно, не все сразу. Пока приведу пример "от жизни" (подобное мы в прошлом обсуждали).
Есть у нас простейшие структуры для которых определены операторы арифметики + - * / Код Пусть контейнеры этих структур принадлежат классу Model. Действуем прямолинейно Код Однако выяснятся что Model может иметь какие-то данные или нет. Напр mUV и/или mColor могут отсутствовать. Также mCoord хотя и есть всегда но может быть vector <Point3d> или vector <Point3f>. Тоже и для mColor (контейнер Point3f или Color3u или Color4u). Поэтому отделаться указателями на конкретные контейнеры не удается. Напрашивается создать универсальный класс Container Код Теперь с Model все прекрасно - она может хранить данные любых форматов в любом кол-ве, напр Код Но как организовать доступ к элементам "обобщенного" контейнера (грубо говоря оператор [])? Напр я хочу сделать метод общий для всех Код Но беда в том что нужного "T" я не имею, а значит и позвать этот метод из класса Model мне нечем Код Остается мучительно "приводить" Container к TypedContainer, что явно плохо Название: Re: Паттерны Variant и Visitor. Тонкости реализации Отправлено: m_ax от Февраль 23, 2015, 23:30 Цитировать Напрашивается создать универсальный класс Container Напрашивается создать (если в будущем не всплывут какие-либо шокирующие умалчиваемые сейчас условия) контейнер, по сути своей аналогичный варианту (не путать с контейнером вариантов или вариантом от различных контейнеров)Т.е. примерно такое: Код И уже его использовать в Model'и.. Если понятно как реализован variant+static_visitor, то с реализацией vector_variant и соответствующего визитёра проблем не должно возникнуть.. Но это, всего лишь один из возможных путей.. Может там элегантнее всё иначе решается.. Название: Re: Паттерны Variant и Visitor. Тонкости реализации Отправлено: Igors от Февраль 24, 2015, 06:45 Код И уже его использовать в Model'и.. Если понятно как реализован variant+static_visitor, то с реализацией vector_variant и соответствующего визитёра проблем не должно возникнуть.. Код: typedef variant_container<vector<Point3d>, vector<Point3f>, vector<Color3u>, vector<Color4u> > my_variant_container_type; Название: Re: Паттерны Variant и Visitor. Тонкости реализации Отправлено: m_ax от Февраль 24, 2015, 09:40 Цитировать Ну пока не очень понятно. Мне нужен "вариант контейнера" (а не "вариант элемента"), поэтому наверно Нет, не вариант контейнеров, а контейнер, который совмещает в своём функционале черты обычного контейнера и variant'а:Код Этот контейнер содержит в себе вектор для какого либо типа из списка: Код Реализуется это аналогично variant'у.. Или, в принципе, можно и просто variant векторов. Цитировать Есть много действий/методов которые совершенно одинаковы для всех контейнеров (см пример Interpolate выше). И что, для каждого надо заводить класс визитора и.т.п.? Хмм... это можно пережить, но как-то не очень. Или я неправильно понял? Наверное не правильно.. Для этого достаточно написать только один метод:Код
Название: Re: Паттерны Variant и Visitor. Тонкости реализации Отправлено: Igors от Февраль 24, 2015, 10:22 Наверное не правильно.. Для этого достаточно написать только один метод: Ну так елы-палы, у меня таких ф-ций много десятков (если не сотни), что же, каждую оборачивать в визитора? :'( :'(Код
И еще, а как если их 2?, напр Код Эл-ты src и dst могут быть одного типа или нет, но в любом случае могут присваиваться друг другу. Название: Re: Паттерны Variant и Visitor. Тонкости реализации Отправлено: m_ax от Февраль 24, 2015, 10:48 Цитировать Ну так елы-палы, у меня таких ф-ций много десятков (если не сотни), что же, каждую оборачивать в визитора? Ну тогда первый вариант: пишите контейнер, который совмещает в своём функционале черты обычного контейнера и variant'аЦитировать Эл-ты src и dst могут быть одного типа или нет, но в любом случае могут присваиваться друг другу. В boost'е и для двух элементов есть реализация визитёра.. Вы документацию читаете, вообще?) |