Не прошло и полугода с момента предыдущей демонстрации того, как шаблоны 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 в очередной раз рулит и бибикает:
template< typename DISPATCHER, typename BINDER_MIXIN > class binder_for_public_disp_template_t : public so_5::rt::disp_binder_t , protected BINDER_MIXIN { public: template< typename... 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 серьезно облегчают жизнь разработчику здесь и сейчас. А не где-то там, в ближайшем будущем ;)
Правда, я далеко не самый лучший программист и многим людям при виде моего кода хочется "обнять и плакать (с)". Так что есть очень ненулевая вероятность, что я не прав ;)
Комментариев нет:
Отправить комментарий