По катом небольшой пример того, как C++ные шаблоны и лямбда функции позволили избавиться от копипасты и, надеюсь, упростили дальнейшее сопровождение кода. Желание показать этот пример появилось после прочтения вот этого комментария в обсуждении свежего выпуска подкаста DevZen.
У меня в коде было несколько пар методов, выполняющих практически одинаковые действия, отличающиеся некоторыми мелкими деталями внутри. Вот как это выглядело совсем недавно:
virtual void subscribe_event_handler( const std::type_index & type_wrapper, const so_5::rt::message_limit::control_block_t * limit, agent_t * subscriber ) override { std::unique_lock< default_rw_spinlock_t > lock( m_lock ); auto it = m_subscribers.find( type_wrapper ); if( it == m_subscribers.end() ) { local_mbox_details::subscriber_adaptive_container_t container; container.emplace( subscriber, limit ); m_subscribers.emplace( type_wrapper, std::move( container ) ); } else { auto & agents = it->second; auto pos = agents.find( subscriber ); if( pos != agents.end() ) { pos->set_limit( limit ); } else agents.emplace( subscriber, limit ); } } |
virtual void set_delivery_filter( const std::type_index & msg_type, const delivery_filter_t & filter, agent_t & subscriber ) override { std::unique_lock< default_rw_spinlock_t > lock( m_lock ); auto it = m_subscribers.find( msg_type ); if( it == m_subscribers.end() ) { local_mbox_details::subscriber_adaptive_container_t container; container.emplace( &subscriber, &filter ); m_subscribers.emplace( msg_type, std::move( container ) ); } else { auto & agents = it->second; auto pos = agents.find( &subscriber ); if( pos != agents.end() ) { pos->set_filter( filter ); } else agents.emplace( &subscriber, &filter ); } } |
Можно откровенно сказать, что методы были получены путем копипасты друг из друга. И, понятное дело, со временем из-за копипасты сопровождать код стало сложнее. Например, когда стали меняться типы контейнеров для хранения информации, то пришлось править тело каждого из методов, повторяя одни и те же модификации, но внимательно следя за тем, чтобы случайно не удалить специфические для конкретного метода действия.
Т.к. сопровождать плохой код дороже, чем переписать, то код был переписан. С использованием шаблонов и лямбда-функций. Теперь эти методы выглядят так:
virtual void subscribe_event_handler( const std::type_index & type_wrapper, const so_5::rt::message_limit::control_block_t * limit, agent_t * subscriber ) override { insert_or_modify_subscriber( type_wrapper, subscriber, [&] { return local_mbox_details::subscriber_info_t{ subscriber, limit }; }, [&]( local_mbox_details::subscriber_info_t & info ) { info.set_limit( limit ); } ); } |
virtual void set_delivery_filter( const std::type_index & msg_type, const delivery_filter_t & filter, agent_t & subscriber ) override { insert_or_modify_subscriber( msg_type, &subscriber, [&] { return local_mbox_details::subscriber_info_t{ &subscriber, &filter }; }, [&]( local_mbox_details::subscriber_info_t & info ) { info.set_filter( filter ); } ); } |
Общая логика вынесена в следующий шаблонный метод:
template< typename INFO_MAKER, typename INFO_CHANGER > void insert_or_modify_subscriber( const std::type_index & type_wrapper, agent_t * subscriber, INFO_MAKER maker, INFO_CHANGER changer ) { std::unique_lock< default_rw_spinlock_t > lock( m_lock ); auto it = m_subscribers.find( type_wrapper ); if( it == m_subscribers.end() ) { local_mbox_details::subscriber_adaptive_container_t container; container.insert( maker() ); m_subscribers.emplace( type_wrapper, std::move( container ) ); } else { auto & agents = it->second; auto pos = agents.find( subscriber ); if( pos != agents.end() ) { changer( *pos ); } else agents.insert( maker() ); } } |
Данный метод объявлен шаблонным потому, что лямбды в C++ -- это объекты какого-то безымянного типа, неизвестного пользователю, сгенериванного актоматически компилятором. И передать лямбду параметром в какой-то метод можно либо задействуя шаблоны, либо через std::function. Но std::function -- это расходы в run-time, тогда как передача лямбды как параметра шаблона, скорее всего, приведет к эффективному инлайнингу тела лямбды в место ее вызова.
PS. Еще несколько примеров устранения копипасты через шаблоны: #1, #2.
Комментариев нет:
Отправить комментарий