Андрей Куприн @akuprin
Студент МГТУ им. Н. Э. Баумана
Информация
- В рейтинге
- Не участвует
- Откуда
- Москва, Москва и Московская обл., Россия
- Зарегистрирован
- Активность
Специализация
Специалист
Git
SQL
Python
Linux
OOP
C++
C
Software development
C++ STL
Applied math
Концепты - отличная штука, согласен, можно с их помощью доработать всё так, чтобы ошибки компиляции выглядели приятнее. А с помощью еще некоторых манипуляций (см. комментарии от пользователя @Kelbon) можно достичь той же гибкости в рантайме.
Дело в том, что мы не всегда можем иметь доступ к классу, который хотим завернуть в такую «типо-старательную» оболочку. Например, если мы хотим обращаться с похожими классами из разных библиотек, как с полиморфными. Классы библиотеки живут своей жизнью, а мы уже в своем коде можем определить функцию-обработчик (просто функцию, не метод класса) для каждого такого класса. Тем самым достигаем желаемого «полиморфизма» при этом не трогая библиотечный код.
Один из примеров из статьи про external polymorphism. Представьте себе, что мы хотим реализовать сериализацию для классов программы (и своих и библиотечных). Мы не можем взять и добавить им в иерархию общего предка с методом serialize(), тк библиотечные классы мы не можем менять. Но зато мы можем создать свою оболочку со стиранием типа для них и отдельно функции, которые уже умеют обрабатывать каждый из желаемых классов.
В общем суть в том, что функции обработчики находятся вне самих обрабатываемых классов по тем или иным причинам. А данный паттерн позволяет не смотря на это связать их и заставить работать вместе так, как будто классы полиморфные.
Ваша реализация тоже хороша, но она слегка другая. У вас стратегия на основе шаблонов. Соотвественно, так как шаблоны инстанцируются при компиляции, она не дает такой гибкости во время исполнения программы. Но с помощью нее тоже можно добиться интересных результатов.
А в моем примере я пишу, можно сказать «классическую» реализацию. Мы выносим вариативное поведение внутри методов класса в отдельную иерархию классов стратегий. Для создания такой иерархии мы первым делом определяем её базовый абстрактный класс. Отсюда и виртуальные методы, отсюда и указатели с полиморфизмом. А уже чтобы не пришлось возиться с временем жизни объектов, я использую умные указатели вместо обычных.
Так что все варианты имеют место быть, просто у каждого свое назначение(см. ссылка), свои плюсы и минусы.
Интересно, спасибо!
Согласен. А то, что вы говорите, слегка упомянуто мной:
См. ссылка
Почему? Объясните, пожалуйста.
Как минимум, код становится красивее и читабельнее! А с
std::variant
могут возникнуть трудности, например: https://godbolt.org/z/633eh11rcА в моём примере из класса фигуры выносится алгоритм её рисования. Считаю, что это всё-таки стратегия. Но прочитав ваш пример понял, что можно решить проблему и с помощью паттерна посетитель. Оба они в рамках данной задачи имеют место быть, просто говорить о них, считаю, нужно в разных местах.
Если идти по тем же шагам, что в статье. На тот момент, когда приводится решение проблемы с помощью стратегии, мы ещё не отказались от наличия метода
draw
в классах фигур.Шаблон стратегия позволяет переключаться между алгоритмами (стратегиями) в зависимости от ситуации. Вот мы и пользуемся этим паттерном:
С другой стороны: Шаблон посетитель позволяет добавлять будущие операции для объектов без их модифицирования. Вот на этапе, когда мы дошли до того, что в классах фигур больше нет метода
draw
(несколькими шагами позже примера со стратегией), думаю можно уже реализовывать визитора. Именно на этом моменте мы перестаём модифицировать классы, с которыми работаем, прямо как в кратком определении визитора, которое я привёл в начале абзаца.В этот момент начинается рассказ о паттерне external polymorphism, поэтому визитор слегка отходит на второй план.
Да, я уже тогда понял, что есть разница, а я сам написал о другом, спасибо! И ваша библиотека, которая делает такое в рантайме, тоже отличная.
Насколько я понял пока изучал ассемблерный код примеров на godbolt.org, в этом деле нам очень сильно могут помочь отпимизации компилятора ("девиртуализация", кажется). (Не ручаюсь за свои слова здесь на 100%, я не эксперт ассемблера).
Как хорошо, что с недавнего времени у нас есть концепты. С ними ошибки намного приятнее читать)
И примеры использования:
https://github.com/thecppzoo/zoo
https://github.com/ldionne/dyno
Boost Type Erasure
Примеры взял отсюда (таймкод 49:00)
А вот в Си такое можно и средствами языка (_Generic) начиная с C11, посмотрите:
Вывод ожидаемый:
К сожалению, если типов сделать больше и ко всем еще добавлять функции, принимающие их, то из этого выйдет мешанина. Но базово сделать все-таки что-то можно!
И не очень понятно, раз мы говорим про C++, то чем плоха специализация шаблонов?