Menu

[ru] Нужна ли поддержка std::bind и какие-то обертки вокруг std::bind?

2015-04-10
2015-04-10
  • Yauheni Akhotnikau

    Лямбда-функции в качестве обработчиков событий зарекомендовали себя как удобный способ сократить объем кода. Как при определении событий обычных агентов. И тем более при определении ad-hoc-агентов.

    Однако, в C++ есть еще один инструмент, который в последнее время широко используется наряду с лямбда-функциями -- это std::bind. Его поддержки в SO-5 нет.

    Когда такая поддержка могла бы быть полезной?

    Например, когда весь обработчик события -- это вызов какой-то внешней функции или метода какого-то стороннего объекта. Скажем, вот в таком случае (пример несколько надуманный, но может быть всякое):

    struct create_hash_request
    {
      const std::uint_t * m_what;
      const std::size_t m_size;
      ... // Конструктор(ы)...
    };
    
    // Интерфейс устройства, которое эффективно выполняет криптографические операции.
    class crypto_device { ... };
    
    // Эти функции будут обеспечивать посторение разных
    // типов хешей с помощью заданного устройства.
    std::string create_md5_hash( crypto_device * cd, const create_hash_request & req ) { ... }
    std::string create_sha1_hash( crypto_device * cd, const create_hash_request & req ) { ... }
    std::string create_sha256_hash( crypto_device * cd, const create_hash_request & req ) { ... }
    
    // Под каждый тип хеша заводим своего агента,
    // который реагирует на запросы из отдельного mbox-а.
    crypto_device * cd = ...;
    env.introduce_coop( [cd]( so_5::rt::agent_coop_t & coop ) {
      ...
      coop.define_agent().event( md5_mbox, [cd]( const create_hash_request & req ) {
          return create_md5_hash( cd, req );
        } );
      coop.define_agent().event( sha1_mbox, [cd]( const create_hash_request & req ) {
          return create_sha1_hash( cd, req );
        } ); 
      coop.define_agent().event( sha256_mbox, [cd]( const create_hash_request & req ) {
          return create_sha256_hash( cd, req );
        } );
    } );  
    

    На мой взгляд, проблема здесь в том, что определение подобных лямбд для ad-hoc агентов -- это лишняя работа, которую можно было бы не делать, если бы можно было писать так:

    env.introduce_coop( [cd]( so_5::rt::agent_coop_t & coop ) {
      using namespace std;
      using namespace std::placeholders;
      ...
      coop.define_agent().event( md5_mbox, bind( create_md5_hash, cd, _1 ) );
      coop.define_agent().event( sha1_mbox, bind( create_sha1_hash, cd, _1 ) );
      coop.define_agent().event( sha256_mbox, bind( create_sha256_hash, cd, _1 ) );
    } );  
    

    Проблема только в том, что так нельзя написать. Т.к. из возвращенного bind-ом объекта нельзя определить ни тип возвращаемого значения, ни тип аргумента (дабы по типу аргумента можно было понять, на какое именно сообщение нужно подписываться).

    Обойти эту проблему можно за счет использования std::function:

    env.introduce_coop( [cd]( so_5::rt::agent_coop_t & coop ) {
      using namespace std;
      using namespace std::placeholders;
      ...
      coop.define_agent().event( md5_mbox,
        function< std::string(const create_hash_request &) >(
          bind( create_md5_hash, cd, _1 ) ) );
      coop.define_agent().event( sha1_mbox, 
        function< std::string(const create_hash_request &) >(
          bind( create_sha1_hash, cd, _1 ) ) );
      coop.define_agent().event( sha256_mbox,
        function< std::string(const create_hash_request &) >(
          bind( create_sha256_hash, cd, _1 ) ) );
    } );  
    

    Что, на мой взгляд, даже хуже использования лямбды.

    Однако, можно сделать тонкую обертку над function+bind и получить что-то вроде:

    env.introduce_coop( [cd]( so_5::rt::agent_coop_t & coop ) {
      using namespace so_5;
      using namespace std;
      using namespace std::placeholders;
      ...
      coop.define_agent().event( md5_mbox,
        bind_handler< std::string(const create_hash_request &) >( create_md5_hash, cd, _1 ) );
      coop.define_agent().event( sha1_mbox, 
        bind_handler< std::string(const create_hash_request &) >( create_sha1_hash, cd, _1 ) );
      coop.define_agent().event( sha256_mbox,
        bind_handler< std::string(const create_hash_request &) >( create_sha256_hash, cd, _1 ) );
    } );  
    

    Либо, чуть более толстую обертку, которую можно будет использовать так:

    env.introduce_coop( [cd]( so_5::rt::agent_coop_t & coop ) {
      using namespace so_5;
      using namespace std;
      using namespace std::placeholders;
      ...
      coop.define_agent().event( md5_mbox,
        bind_handler< create_hash_request, std::string >( create_md5_hash, cd, _1 ) );
      coop.define_agent().event( sha1_mbox, 
        bind_handler< create_hash_request, std::string >( create_sha1_hash, cd, _1 ) );
      coop.define_agent().event( sha256_mbox,
        bind_handler< create_hash_request, std::string >( create_sha256_hash, cd, _1 ) );
    } );  
    

    Т.е. тут есть где покопаться. Однако первый вопрос вот какой: А нужно ли это вообще?

    И, если нужно, то какой из вариантов предпочтительнее?

     
    • Boris Sivko

      Boris Sivko - 2015-04-10

      Лично я не вижу достаточных плюшек для того, чтобы вводить ещё один способ делать одно и то же.

       
  • Nicolai Grodzitski

    Вот это выглядит отлично:

    coop.define_agent().event( md5_mbox, bind( create_md5_hash, cd, _1 ) );
    

    А вот появление нового класса, который шаблонный, да еще типы надо указывать:

    coop.define_agent().event( sha256_mbox,
        bind_handler< create_hash_request, std::string >( create_sha256_hash, cd, _1 ) );
    

    А нужно ли это вообще?

    Лично я, в этом случае, буду использовать лямбду, раз первый вариант невозможен.

     

    Last edit: Nicolai Grodzitski 2015-04-10
    • Yauheni Akhotnikau

      Типы, к сожалению, нужно указывать по той же причине, по которой нужно указывать типа аргумента лямбды. Т.е. вот отсюда: event(mbox, [](const type &){...}) имя type вынуждено перекочевать вот сюда: bind_handler<type,...>(...).

      У лямбд есть большое преимущество в том плане, что тип возвращаемого значения компилятор выводит сам. В случае же bind_handler его придется указать (если он отличен от void).

      С другой стороны, у лямбды будут недостатки при больших capture-list-ах:

      coop.define_agent().event( channel,
        [channel, device, hash_type, logger]( const request & evt ) {
          worker( channel, device, hash_type, logger, evt );
        } );
      

      против:

      coop.define_agent().event( channel,
        bind_handler< request >( channel, device, hash_type, logger, _1 ) );
      

      Как раз то, что capture-list-ы могут быть длинными и заставило задуматься о bind_handler-е.

       
      • Nicolay Shmakov

        Nicolay Shmakov - 2015-04-10

        Мне кажется, что если в качестве главной претензии к лямбдам будет длинный capture-list, то наверное такая функциональность не нужна, потому что при желании сэкономить на символах, можно просто написать [&]. С другой стороны, много работ над интерфейсом SO выполняется после опроса общественного мнения, а значит аргумент в начале темы "В c++ широко используется, но в SO такой функциональности нет" в долгосрочной перспективе тоже может стать весомее.
        Ну а если лично мое мнение: лямбды мне видятся более удобным средством.

         

Log in to post a comment.