Страницы

Поиск по вопросам

Показаны сообщения с ярлыком asio. Показать все сообщения
Показаны сообщения с ярлыком asio. Показать все сообщения

пятница, 27 декабря 2019 г.

Может ли std::bind быть удобнее, чем лямбды?

#cpp #stl #boost #asio


Поковырялся с asio, и наткнулся вот на какой момент. Для привязки handle'ров к функциям
асинхронного чтения/записи используется bind: в примерах - из boost, я без особых проблем
заменил на stl. Например:

socket_.async_send_to(asio::buffer(*message), remote_endpoint_,
      boost::bind(&udp_server::handle_send, this, message,
        asio::placeholders::error,
        asio::placeholders::bytes_transferred));


И ниже по тексту:

void handle_send(boost::shared_ptr /*message*/,
  const asio::error_code& /*error*/,
  std::size_t /*bytes_transferred*/)  {  }


Но в процессе "щупания" изрядную часть кода я переписал (в первую очередь для отвязки
от boost - шеф и на header-only версию asio смотрит косо). И везде намного удобнее
было написать лямбду:

socket_.async_send_to(asio::buffer(*message),remote_endpoint_,
                    [](const asio::error_code&,std::size_t){});


Поэтому вопрос такой: есть ли какие-то области применения, где std::bind или boost::bind
остаётся единственным или более удобным способом, чем лямбда-выражения? Стоит ли подробно
изучать bind, или достаточно на пальцах представлять, чтобы при необходимости разобраться
в чужом коде - а в своём он не понадобится?
    


Ответы

Ответ 1



Насколько мне кажется, по удобству синтаксиса создание функциональных объектов через std::bind и тому подобное проигрывает по выразительности лямбдам. Тем не менее, я бы всё равно разобрался в том, как работает std::bind (и даже попробовал бы реализовать его вручную, чтобы прочувствовать проблематику). Знания карман не тянут. Кроме этого, возможное применение старой функциональности: чтение и понимание чужого кода программирование под среду, которая имплементирует старый стандарт не исключено, что на вашей платформе старый метод будет производительнее, и это будет критично для вашего кода

суббота, 30 ноября 2019 г.

Не пойму, что делает функция bind() в коде

#cpp #boost #asio


Здравствуйте, недавно начал изучать Boost.Asio, и в одном из примеров не могу понять,
что делает функция bind() в коде данной программы.
Если кто знает, для чего придназначена эта функция, пожалуйста, объясните. Так как
я недавно начал углубляться в библиотеку STL и Boost. Заранее спасибо.

io_service service;
size_t read_complete(char * buff, const error_code & err, size_t bytes) 
{
    if ( err) return 0;
    bool found = std::find(buff, buff + bytes, '\n') < buff + bytes;
    // we read one-by-one until we get to enter, no buffering
    return found ? 0 : 1;
}

void handle_connections() 
{
    ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001));
    char buff[1024];
    while ( true) 
    {
        ip::tcp::socket sock(service);
        acceptor.accept(sock);
        int bytes = read(sock, buffer(buff), boost::bind(read_complete,buff,_1,_2));
        std::string msg(buff, bytes);
        sock.write_some(buffer(msg));
        sock.close();
    }
}

int main(int argc, char* argv[]) 
{
    handle_connections();
}

    


Ответы

Ответ 1



std:bind (я буду говорить об std::bind, так как эта функция перенесена в стандарт C++ из boost) - это адаптер функциональных объектов, который позволяет адаптировать функциональные объекты под заданное число параметров. Чтобы было более ясно ее назначение, допустим вы решили написать функцию вывода произвольного целочисленного массива в поток std::cout . Ваша программа могла бы выглядеть следующим образом: #include void display( const int a[], size_t n ) { for ( size_t i = 0; i < n; i++ ) { std::cout << a[i] << ' '; } std::cout << std::endl; } int main() { const size_t N = 10; int a[N] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; display( a, N ); } Вывод программы на консоль: 1 2 3 4 5 6 7 8 9 10 Но затем вам пришла в голову идея усовершенствовать функцию вывода таким образом, чтобы она не просто выводила элементы массива, как они есть, но и делала какие-то с ними преобразования перед выводом. Поэтому вы решили добавить параметр в функцию, который будет представлять некоторую операцию над элементами массива. Этот третий параметр вы объявили как функцию с одним параметром. Используя эту функцию, вы можете, например, вывести на консоль удвоенные значения элементов массива с помощью лямбда-выражения: #include void display( const int a[], size_t n, int operation( int ) ) { for ( size_t i = 0; i < n; i++ ) { std::cout << operation( a[i] ) << ' '; } std::cout << std::endl; } int main() { const size_t N = 10; int a[N] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; auto doubled = []( int x ) { return 2 * x; }; display( a, N, doubled ); } Вывод на консоль будет 2 4 6 8 10 12 14 16 18 20 В данном случае возможно использовать лямбда-выражение doubled, потому что оно может быть неявно преобразовано в функцию с одним параметром, так как не имеет замыкания. Но, допустим, вы решили выводить на печать не только удвоенные значения элементов массива, но и значения элементов, умноженных на некоторый коэффициент, который задается во время выполнения программы. В этом случае ваша программа могла бы выглядеть следующим образом #include void display( const int a[], size_t n, int operation( int ) ) { for ( size_t i = 0; i < n; i++ ) { std::cout << operation( a[i] ) << ' '; } std::cout << std::endl; } int main() { const size_t N = 10; int a[N] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int factor = 2; auto multiplies = [&factor]( int x ) { return factor * x; }; display( a, N, multiplies ); factor = 3; display( a, N, multiplies ); } Однако она не будет компилироваться. Проблема заключается в том, что если лямбда-выражение содержит замыкание, auto multiplies = [&factor]( int x ) { return factor * x; }; ^^^^^^^^^ то оно не имеет неявного преобразования в указатель на функцию. Поэтому придется написать шаблонную функцию, которая будет иметь шаблонный параметр операции и в качестве аргумента может принимать не только функции, но и объекты классов, который имеют оператор вызова функции operator (). Ваша программа будет выглядеть следующим образом: #include template void display( const int a[], size_t n, Operation operation ) { for ( size_t i = 0; i < n; i++ ) { std::cout << operation( a[i] ) << ' '; } std::cout << std::endl; } int main() { const size_t N = 10; int a[N] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int factor = 2; auto multiplies = [&factor]( int x ) { return factor * x; }; display( a, N, multiplies ); factor = 3; display( a, N, multiplies ); } Вывод на консоль соответственно будет: 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 Но теперь, допустим, вы решили вывести на печать квадраты значений элементов массива. Вы опять бы могли использовать лямбда-выражение, как, например, auto_square = []( int x ){ return x * x; }; И ваша программа успешно бы выполнилась, используя это лямбда-выражение. Но вы знаете, что стандарт уже имеет функциональный объект std::multiplies, который осуществляет операцию x * y для двух аргументов своего оператора вызова функции operator (). Как им воспользоваться? Оператор этого функционального объекта принимает два аргумента, тогда как в нашей функции display функциональный объект принимает только один аргумент. Для этого используются функциональные адаптеры и, в частности, std::bind. Он может "превратить" функциональный объект std::multiplies из объекта, который принимает два аргумента в объект, который принимает один аргумент. Программа будет выглядеть следующим образом: #include #include template void display( const int a[], size_t n, Operation operation ) { for ( size_t i = 0; i < n; i++ ) { std::cout << operation( a[i] ) << ' '; } std::cout << std::endl; } int main() { const size_t N = 10; int a[N] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; auto square = bind( std::multiplies(), std::placeholders::_1, std::placeholders::_1 ); display( a, N, square ); std::cout << std::endl; } Вывод на консоль будет 1 4 9 16 25 36 49 64 81 100 Внутри функции display объект square вызывается как square( a[i] ), который в свою очередь делегирует работу объекту класса std::multiplies, вызывая его как std::multiplies()( a[i], a[i] ); Конечно конструктор std::multiplies не вызывается, так как объект данного класса уже был создан, когда создавался объект square auto square = bind( std::multiplies(), std::placeholders::_1, std::placeholders::_1 ); Вызывался только operator () этого объекта. В вашем примере вызов read требует в качестве третьего аргумента функциональный объект, который принимает только два аргумента: int bytes = read(sock, buffer(buff), boost::bind(read_complete,buff,_1,_2)); Однако, вы хотите, чтобы вызывалась функция read_complete, которая принимает три аргумента вместо двух. В этом случае используется адаптер bind, который сам получает два аргумента, как того требует вызов функции read, но делегирует работу функции read_complete, передавая ей три аргумента: два своих, обозначенных как _1 и _2, и которые ей передает внутри своего тела функция read, когда ее вызывает, и один дополнительный, buff, который она сохранила внутри себя, когда использовалась в качестве аргумента вызова read.

Ответ 2



bind это немного интересная функция (не путать с функцией работы сокетами, которая также называется bind!!!). Ее задача создать одну функцию на базе другой, подставив часть аргументов (это называется "частичным применением"). Посмотрим глубже. Функция read требует трех параметров - сокета, буфера для данных и "функции завершения" (вообще то у этой бустовой read всего восемь различных перегруженных вариантов). Эта функция будет вызываться, что бы определить, что данных прочитано достаточно. Там же в справке приведена сигнатура этой функции. У нее два параметра: код ошибки последнего чтения и сколько байт прочитано. Но что делать, если нет подходящей функции? Для этого есть bind. Он первым аргументом принимает функцию (в данном случае read_complete, а дальше аргументы. Так как исходная функция read_complete имела три аргумента, а функции read нужно только два, то один аргумент подставляется явно (первый), а вместо двух других - заглушки специального вида. В результате, когда функции read нужно будет вызвать функцию завершения, она вызовет read_complete, первый аргумент ей подставит bind, а два вторых будут подставлены с аргументов. Если попробовать написать код схематически, то все выглядит где то так io_service service; size_t read_complete(char * buff, const error_code & err, size_t bytes) { if ( err) return 0; bool found = std::find(buff, buff + bytes, '\n') < buff + bytes; // we read one-by-one until we get to enter, no buffering return found ? 0 : 1; } char buff[1024]; size_t new_func(const error_code & err, size_t bytes) { return read_complete(buff, err, bytes); } void handle_connections() { ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001)); while ( true) { ip::tcp::socket sock(service); acceptor.accept(sock); int bytes = read(sock, buffer(buff), new_func); std::string msg(buff, bytes); sock.write_some(buffer(msg)); sock.close(); } } Как видите, мне пришлось сделать буфер глобальным, что бы новоиспеченная функция имела к нему доступ. На самом деле переменная глобальной не становится, но это был самый простой способ эмулировать поведение. Эта функция bind пришла в с++ с функциональных языков вида haskell и лисп, где подобное встречается на каждом шагу.

среда, 5 декабря 2018 г.

Может ли std::bind быть удобнее, чем лямбды?

Поковырялся с asio, и наткнулся вот на какой момент. Для привязки handle'ров к функциям асинхронного чтения/записи используется bind: в примерах - из boost, я без особых проблем заменил на stl. Например:
socket_.async_send_to(asio::buffer(*message), remote_endpoint_, boost::bind(&udp_server::handle_send, this, message, asio::placeholders::error, asio::placeholders::bytes_transferred));
И ниже по тексту:
void handle_send(boost::shared_ptr /*message*/, const asio::error_code& /*error*/, std::size_t /*bytes_transferred*/) { }
Но в процессе "щупания" изрядную часть кода я переписал (в первую очередь для отвязки от boost - шеф и на header-only версию asio смотрит косо). И везде намного удобнее было написать лямбду:
socket_.async_send_to(asio::buffer(*message),remote_endpoint_, [](const asio::error_code&,std::size_t){});
Поэтому вопрос такой: есть ли какие-то области применения, где std::bind или boost::bind остаётся единственным или более удобным способом, чем лямбда-выражения? Стоит ли подробно изучать bind, или достаточно на пальцах представлять, чтобы при необходимости разобраться в чужом коде - а в своём он не понадобится?


Ответ

Насколько мне кажется, по удобству синтаксиса создание функциональных объектов через std::bind и тому подобное проигрывает по выразительности лямбдам.
Тем не менее, я бы всё равно разобрался в том, как работает std::bind (и даже попробовал бы реализовать его вручную, чтобы прочувствовать проблематику). Знания карман не тянут.
Кроме этого, возможное применение старой функциональности:
чтение и понимание чужого кода программирование под среду, которая имплементирует старый стандарт не исключено, что на вашей платформе старый метод будет производительнее, и это будет критично для вашего кода