#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 . Ваша программа могла бы выглядеть следующим образом: #includevoid 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 и лисп, где подобное встречается на каждом шагу.
Комментариев нет:
Отправить комментарий