Очередная часть серии "шаблоны против копипасты", которая, как обычно, получилась в результате рефакторинга имевшегося и работавшего кода (предыдущая часть серии здесь). Правда, в этом случае я буду показывать не реальный код, ибо тогда пост получится перегруженными никому не интересными деталями. А уже максимально упрощенная и сокращенная версия. Но все равно получилось довольно объемно, поэтому все фрагменты кода упрятаны под кат.
Итак, суть в том, что было несколько семейств функций send, send_delayed и send_periodic. В каждом семействе была одна "как бы главная" функция, получавшая самый общий набор аргументов. А все остальные функции были всего лишь обертками вокруг главной функции, но обертками, заточенными под определенный тип получателя. Обертки эти нужны были для того, чтобы сделать код, использующий send-ы, единообразным. Чтобы отсылка сообщения в какой-то конкретный mbox синтаксически не отличалась от отсылки сообщения в какой-то mchain. Ибо такое синтаксическое однообразие очень и очень сильно упрощает написание шаблонного кода.
В общем, за несколько лет развития данные семейства функций приобрели, схематично, следующий вид:
// Первое семейство функций. // template<typename... ARGS> void send(mbox_t & dest, ARGS &&... args) {...} template<typename... ARGS> void send(agent_t & dest, ARGS &&... args) { send(dest.so_direct_mbox(), forward<ARGS>(args)...); } template<typename... ARGS> void send(adhoc_agent_t & dest, ARGS &&... args) { send(dest.direct_mbox(), forward<ARGS>(args)...); } template<typename... ARGS> void send(mchain_t & dest, ARGS &&... args) { send(dest.as_mbox(), forward<ARGS>(args)...); } // Второе семейство функций. // template<typename... ARGS> void send_delayed(env_t & env, mbox_t & dest, duration pause, ARGS &&... args) {...} template<typename... ARGS> void send_delayed(agent_t & dest, duration pause, ARGS &&... args) { send_delayed(dest.so_environment(), dest.so_direct_mbox(), pause, forward<ARGS>(args)...); } template<typename... ARGS> void send_delayed(adhoc_agent_t & dest, duration pause, ARGS &&... args) { send_delayed(dest.environment(), dest.direct_mbox(), pause, forward<ARGS>(args)...); } template<typename... ARGS> void send_delayed(mchain_t & dest, duration pause, ARGS &&... args) { send_delayed(dest.environment(), dest.as_mbox(), pause, forward<ARGS>(args)...); } // Третье семейство функций. // template<typename... ARGS> timer_t send_periodic(env_t & env, mbox_t & dest, duration pause, duration period, ARGS &&... args) {...} template<typename... ARGS> timer_t send_periodic(agent_t & dest, duration pause, duration period, ARGS &&... args) { return send_periodic(dest.so_environment(), dest.so_direct_mbox(), pause, period, forward<ARGS>(args)...); } template<typename... ARGS> timer_t send_periodic(adhoc_agent_t & dest, duration pause, duration period, ARGS &&... args) { return send_periodic(dest.environment(), dest.direct_mbox(), pause, period, forward<ARGS>(args)...); } template<typename... ARGS> timer_t send_periodic(mchain_t & dest, duration pause, duration period, ARGS &&... args) { return send_periodic(dest.environment(), dest.as_mbox(), pause, period, forward<ARGS>(args)...); } |
На самом деле все несколько сложнее и перегруженных функций чуть-чуть больше, но смысл именно такой.
Расширение каждого семейства происходило постепенно, поэтому изначально задача минимизации повторяющегося кода актуальной не была. Просто добавилась такая штука, как adhoc_agent_t, добавились три новых варианта send, send_delayed и send_periodic. Добавился mchain -- появились еще три варианта... Но вот когда все уже существующие варианты потребовалось чуть-чуть доработать до поддержки новой функциональности, то тут выяснилось, что обилие повторяющегося кода -- это совсем не хорошо.
Чтобы упростить самому себе работу было решено отказаться от создания функций-оберток для каждого типа получателя. По хорошему, в каждом из семейств функций должно было остаться всего по две функции: одна главная (она же универсальная) и вторая шаблонная, которая делегирует всю работу главной функции.
Оказалось, что это совсем не сложно. Нужно было всего лишь решить вопрос с тем, как из конкретного типа аргумента dest вытащить актуальные параметры, необходимые для главной универсальной функции. Здесь на помощь приходит старый-добрый механизм перегрузки функций в С++. Просто делается набор вот таких вспомогательных трансформаторов:
// Вспомогательные функции-конвертеры. // namespace details { inline mbox_t & to_mbox(agent_t & dest) { return dest.so_direct_mbox(); } inline mbox_t & to_mbox(adhoc_agent_t & dest) { return dest.direct_mbox(); } inline mbox_t & to_mbox(mchain_t & dest) { return dest.as_mbox(); } inline env_t & to_env(agent_t & dest) { return dest.so_environment(); } inline env_t & to_env(adhoc_agent_t & dest) { return dest.environment(); } inline env_t & to_env(mchain_t & dest) { return dest.environment(); } } |
Ну а с их помощью создание шаблонных send, send_delayed и send_periodic уже оказывается делом техники. В итоге все три семейства функций начинают выглядеть вот так:
// Первое семейство функций. // template<typename... ARGS> void send(mbox_t & dest, ARGS &&... args) {...} template<typename D, typename... ARGS> void send(D & dest, ARGS &&... args) { send(details::to_mbox(dest), forward<ARGS>(args)...); } // Второе семейство функций. // template<typename... ARGS> void send_delayed(env_t & env, mbox_t & dest, duration pause, ARGS &&... args) {...} template<typename D, typename... ARGS> void send_delayed(D & dest, duration pause, ARGS &&... args) { send_delayed(details::to_env(dest), details::to_mbox(dest), pause, forward<ARGS>(args)...); } // Третье семейство функций. // template<typename... ARGS> timer_t send_periodic(env_t & env, mbox_t & dest, duration pause, duration period, ARGS &&... args) {...} template<typename D, typename... ARGS> timer_t send_periodic(D & dest, duration pause, duration period, ARGS &&... args) { return send_periodic(details::to_env(dest), details::to_mbox(dest), pause, period, forward<ARGS>(args)...); } |
Что уже гораздо компактнее, а значит проще и дешевле в сопровождении. И, что очень немаловажно, в документировании :)
PS. Кстати говоря, за время программирования на C++ настолько привык к наличию перегруженных функций, что языки, в которых этого нет, вызывают серьезную ломку. Особенно когда в них в той или иной мере присутствует обобщенное программирование. Как по мне, так шаблоны (генерики) без перегрузки функций сильно снижают выразительность языка.
Комментариев нет:
Отправить комментарий