понедельник, 17 августа 2015 г.

[prog.с++11] Шаблоны против копипасты-2 :)

Не прошло и полугода с момента предыдущей демонстрации того, как шаблоны C++ могут избавить от дублирования кода, и вот появился совсем свеженький пример. Как обычно, сначала был написан один простой класс. Потом из него был сделан еще один простой, но чуть-чуть другой. Потом еще один и т.к. В какой-то момент захотелось эту порочную практику прекратить. К тому же стало лучше понятно, как это сделать.

Итак, было что-то вроде вот таких братьев-близнецов:

class disp_binder_t
  : public so_5::rt::disp_binder_t
  , protected binding_actions_t
  {
  public:
    disp_binder_t(
      const std::string & disp_name )
      : m_disp_name( disp_name )
      {}

    virtual so_5::rt::disp_binding_activator_t
    bind_agent(
      so_5::rt::environment_t & env,
      so_5::rt::agent_ref_t agent ) override
      {
        using namespace so_5::disp::reuse;

        return do_with_dispatcher< dispatcher_t >(
          env,
          m_disp_name,
          [agent]( dispatcher_t & disp )
          {
            return do_bind( disp, std::move( agent ) );
          } );
      }

    virtual void
    unbind_agent(
      so_5::rt::environment_t & env,
      so_5::rt::agent_ref_t agent ) override
      {
        using namespace so_5::disp::reuse;

        do_with_dispatcher< dispatcher_t >( env, m_disp_name,
          [agent]( dispatcher_t & disp )
          {
            do_unbind( disp, std::move( agent ) );
          } );
      }

  private:
    //! Name of the dispatcher to be bound to.
    const std::string m_disp_name;
  };
class disp_binder_t
  : public so_5::rt::disp_binder_t
  , protected binding_actions_t
  {
  public :
    disp_binder_t(
      std::string disp_name,
      params_t params )
      : binding_actions_t( std::move( params ) )
      , m_disp_name( std::move( disp_name ) )
      {}

    virtual disp_binding_activator_t
    bind_agent(
      environment_t & env,
      agent_ref_t agent )
      {
        using namespace so_5::disp::reuse;

        return do_with_dispatcher< dispatcher_t >(
          env,
          m_disp_name,
          [this, agent]( dispatcher_t & disp )
          {
            return do_bind( disp, std::move( agent ) );
          } );
      }

    virtual void
    unbind_agent(
      environment_t & env,
      agent_ref_t agent )
      {
        using namespace so_5::disp::reuse;

        do_with_dispatcher< dispatcher_t >( env, m_disp_name,
          [this, agent]( dispatcher_t & disp )
          {
            do_unbind( disp, std::move( agent ) );
          } );
      }

  private :
    //! Name of dispatcher to bind agents to.
    const std::string m_disp_name;
  };

Разница состояла в том, что binding_actions_t, от которых наследовались эти два disp_binder_t, несколько отличались друг от друга:

class binding_actions_t
  {
  protected :
    static so_5::rt::disp_binding_activator_t
    do_bind(
      dispatcher_t & disp,
      so_5::rt::agent_ref_t agent )
      {
        auto ctx = disp.create_thread_for_agent( *agent );

        return so_5::details::do_with_rollback_on_exception(
            [&] {
              return so_5::rt::disp_binding_activator_t{
                [agent, ctx]() {
                  agent->so_bind_to_dispatcher( *ctx );
                } };
            },
            [&] {
              // Dispatcher for the agent should be removed.
              disp.destroy_thread_for_agent( *agent );
            } );
      }

    static void
    do_unbind(
      dispatcher_t & disp,
      so_5::rt::agent_ref_t agent )
      {
        disp.destroy_thread_for_agent( *agent );
      }
  };
class binding_actions_t
  {
  protected :
    binding_actions_t( params_t params )
      : m_params( std::move( params ) )
      {}

    so_5::rt::disp_binding_activator_t
    do_bind(
      dispatcher_t & disp,
      so_5::rt::agent_ref_t agent )
      {
        auto queue = disp.bind_agent( agent, m_params );

        return [queue, agent]() {
            agent->so_bind_to_dispatcher( *queue );
          };
      }

    void
    do_unbind(
      dispatcher_t & disp,
      agent_ref_t agent )
      {
        disp.unbind_agent( std::move( agent ) );
      }

  private :
    const params_t m_params;
  };

То, что binding_actions_t разные -- это нормально. Они остались практически в таком же виде. За исключением небольших изменений: там, где методы do_bind и do_unbind были статическими, они стали обычными методами.

А вот объявление disp_binder_t существенным образом сократилось:

using disp_binder_t =
  so_5::disp::reuse::binder_for_public_disp_template_t<
    dispatcher_t, binding_actions_t >;
using disp_binder_t =
  so_5::disp::reuse::binder_for_public_disp_template_t<
    dispatcher_t, binding_actions_t >;

Как только нашлось время немного подумать, оказалось, что написать шаблон binder_for_public_disp_template_t не сложно. Единственный более-менее серьезный момент -- это факт того, что у разных binding_actions_t разные наборы параметров в конструкторах. Но это решилось за счет того, что у шаблонного класса binder_for_public_disp_template_t конструктор так же шаблонный! Да еще и написанный с использованием variadic templates! В общем, C++11 в очередной раз рулит и бибикает:

templatetypename DISPATCHER, typename BINDER_MIXIN >
class binder_for_public_disp_template_t
  : public so_5::rt::disp_binder_t
  , protected BINDER_MIXIN
  {
  public:
    templatetypename... BINDER_MIXIN_ARGS >
    binder_for_public_disp_template_t(
      std::string disp_name,
      BINDER_MIXIN_ARGS &&... args )
      : BINDER_MIXIN( std::forward<BINDER_MIXIN_ARGS>(args)... )
      , m_disp_name( std::move( disp_name ) )
      {}

    virtual so_5::rt::disp_binding_activator_t
    bind_agent(
      so_5::rt::environment_t & env,
      so_5::rt::agent_ref_t agent ) override
      {
        return do_with_dispatcher< DISPATCHER >(
          env,
          m_disp_name,
          [this, agent]( DISPATCHER & disp )
          {
            return this->do_bind( disp, std::move( agent ) );
          } );
      }

    virtual void
    unbind_agent(
      so_5::rt::environment_t & env,
      so_5::rt::agent_ref_t agent ) override
      {
        using namespace so_5::disp::reuse;

        do_with_dispatcher< DISPATCHER >( env, m_disp_name,
          [this, agent]( DISPATCHER & disp )
          {
            this->do_unbind( disp, std::move( agent ) );
          } );
      }

  private:
    const std::string m_disp_name;
  };

В качестве небольшого послесловия. Лично мне такие фокусы с C++ными шаблонами говорят о том, что новоявленным убивцам C++, вроде Rust или Go, нужно еще расти и расти. Да желательно побыстрее. А то ведь C++11/14 серьезно облегчают жизнь разработчику здесь и сейчас. А не где-то там, в ближайшем будущем ;)

Правда, я далеко не самый лучший программист и многим людям при виде моего кода хочется "обнять и плакать (с)". Так что есть очень ненулевая вероятность, что я не прав ;)

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