воскресенье, 15 ноября 2015 г.

[prog.c++11] Еще один пример использования шаблонов и лямбд для устранения копипасты

По катом небольшой пример того, как 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 );
            } );
   }

Общая логика вынесена в следующий шаблонный метод:

templatetypename 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.

Комментариев нет: