Лямбда-функции появились в C++11. Они представляют собой анонимные функции, которые можно определить в любом месте программы, подходящем по смыслу.
Приведу пример простейшей лямбда функции:
auto myLambda = [](){ std::cout << "Hello, lambda!" << std::endl; };
myLambda();
Выражение auto myLambda
означает объявление переменной с автоматически определяемым типом. Крайне удобная конструкция C++11, которая позволяет сделать ваш код более лаконичным и устойчивым к изменениям. Настоящий тип лямбда-функции слишком сложный, поэтому набирать его нецелесообразно.
Непосредственное объявление лямбда-функции [](){ std::cout << "Hello, lambda!" << std::endl; }
состоит из трех частей. Первая часть (квадратные скобки []
) позволяет привязывать переменные, доступные в текущей области видимости. Вторая часть (круглые скобки ()
) указывает список принимаемых параметров лямбда-функции. Третья часть (в фигурных скобках {}
) содержит тело лямбда-функции.
Вызов определенной лямбда-функции ничем не отличается от вызова обычной функции: myLambda()
. В нашем случае на консоль будет выведено сообщение:
"Hello, lambda!"
Прежде, чем перейти к более сложным примерам лямбда-функций, определим вспомогательную шаблонную функцию:
template< typename Func >
void call( Func func ) {
func( -5 );
}
В качестве аргумента она принимает любой объект, который можно вызвать с аргументом -5
. Более подробно о создании таких функций мы говорили, когда рассматривали указатели на функции в C++. Мы будем передавать в call()
наши лямбда-функции для запуска.
Сначала просто выведем переданное лямбда-функции значение:
call(
[]( int val ) { // Параметр val == -5, т.е. соответствует переданному значению
std::cout << val << std::endl; // --> -5
}
);
Теперь рассмотрим возможность "замыкания", т.е. передадим в лямбда-функцию значение локальной переменной по значению:
int x = 5;
call(
[ x ]( int val ) { // x == 5, но параметр передается по значению
std::cout << x + val << std::endl; // --> 0
// x = val; - Не компилируется. Изменять значение x мы не можем
}
);
Но если мы хотим изменить значение переменной внутри лямбда-функции, то можем передать его по ссылке:
int x = 33;
call(
[ &x ]( int val ) { // Теперь x передается по ссылке
std::cout << x << std::endl; // --> 33
x = val; // OK!
std::cout << x << std::endl; // --> -5
}
);
// Значение изменилось:
std::cout << x << std::endl; // --> -5
Обратите внимание на побочный эффект от связывания переменных с лямбда-функцией по ссылке:
int x = 5;
auto refLambda = [ &x ](){ std::cout << x << std::endl; };
refLambda(); // --> 5
x = 94;
// Значение поменялось, что скажется и на лямбда-функции!
refLambda(); // --> 94
Будьте особенно аккуратны с привязкой параметров по ссылке, когда работаете с циклами. Чтобы не получилось, что все созданные лямбда-функции работали с одним и тем же значением, когда должны иметь собственные копии.
Привязку можно осуществлять по любому числу переменных, комбинируя как передачу по значению, так и по ссылке:
int y = 55;
int z = 94;
call(
[ y, &z ]( int val ) {
std::cout << y << std::endl; // --> 55
std::cout << z << std::endl; // --> 94
// y = val; - Нельзя
z = val; // OK!
}
);
Если требуется привязать сразу все переменные, то можно использовать следующие конструкции:
call(
// Привязываем все переменные в области видимости по значению
[ = ]( int val ){ /* … */ }
);
call(
// Привязываем все переменные в области видимости по ссылке
[ & ]( int val ){ /* … */ }
);
Допустимо и комбинирование:
int x = 21;
int y = 55;
int z = 94;
int w = 42;
call(
// Привязываем x по значению, а все остальное по ссылке
[ &, x ]( int val ){ /* … */ }
);
call(
// Привязываем y по ссылке, а все остальное по значению
[ =, &y ]( int val ){ /* … */ }
);
call(
// Привязываем x и w по ссылке, а все остальное по значению
[ =, &x, &w ]( int val ){ /* … */ }
);
Однако замечу, что на практике лучше не использовать обобщенное привязывание через =
и &
, а явно обозначать необходимые переменные по одной. Иначе могут возникнуть загадочные ошибки из-за конфликтов имен.
Один из лучших примеров правильного использования лямбда-функций связан с библиотекой алгоритмов stl
. Большинство функций этой библиотеки принимают аргумент-предикат. Такой аргумент позволяет контролировать те или иные аспекты алгоритма.
Конечно, этот аргумент не обязан быть лямбда-функцией, но часто их применение оказывается оправданным. Например:
std::vector< int > v = { 2, 4, 5, 6, 7, 9, 11, 14 };
auto end = std::remove_if( v.begin(), v.end(), []( int x ) { return x % 2 == 0; } );
std::for_each( v.begin(), end, []( int x ) { std::cout << x << " "; } );
Этот код интуитивно понятен, поэтому вы сами с ним легко разберетесь без моих пояснений. Приведу лишь то, что он выведет на консоль:
5 7 9 11
В своих программах вы тоже можете создавать универсальные функции, для которых управление ходом выполнения осуществляется с помощью функциональных объектов и лямбда-функций в частности.
Anonymous:
"Интуитивно понятна" - это inline функция. А нахрена заморачиваться со всякими лямбда - нифига не понятно. Хотя для "говнокода" - в самый раз. Имхо.
Согласен, что пример несколько притянут. Может потом подберу более подходящий. Приведенный является вариацией примера для std::remove_if с cplusplus.com.
А если речь идет о том, что лямбда-функции не нужны вовсе, то технически это, конечно, так. В конце концов, всегда можно создать функтор. Однако замыкание в комбинации с лямбда-функциями позволяет сделать код менее многословным.
Лямда фнкция дает искушение написать оные 100500 раз в коде программы и превратить оную в превдосложный-мегакод. Вместо того, чтобы написать отдельно функцию, мы ее "по-быстрому" слабаем в теле другой функции. По принципу; "не успеваю сделать нормально - план горит". Имхо.
Anonymous:
Лямда фнкция дает искушение написать оные 100500 раз в коде программы и превратить оную в превдосложный-мегакод. Вместо того, чтобы написать отдельно функцию, мы ее "по-быстрому" слабаем в теле другой функции. По принципу; "не успеваю сделать нормально - план горит". Имхо.
Ну да. Тут, как и в любом деле, нужно знать меру. Если можно написать более общее решение, которое получится использовать в коде многократно, то так и следует поступать.
В частности, для примера из статьи с проверкой на четность можно создать функтор на подобии <typename T>IsDivisibleBy. Чтобы использовать его по нужному месту с аргументом:
std::remove_if( v.begin(), v.end(), IsDivisibleBy< int >( 2 ) );
В этом смысле злоупотребление лямбда-функциями аналогично использованию "волшебных чисел". С другой стороны, если есть уверенность, что требуется очень специфическая функция, которая больше в коде нигде не понадобится в обозримом будущем, то лямбдой вполне можно обойтись (например, для какого-нибудь callback'а). Хотя, конечно, все часто идет не по плану, и функция где-нибудь может понадобиться вновь. В этом случае дублированием заниматься неуместно, и без рефакторинга не обойтись.
Очень интересно!
int array[] = { 1, 2, 3, 4, 5, 6 };
int x = 5[array];
Как это работает? Где тело лямбды и что за синтакс индекса перед массивом?
В приведенном фрагменте кода лямбды не используются. А запись
int x = 5[array];
можно представить в виде:
int x = *( 5 + array ); // или *( array + 5 )
что равносильно:
int x = array[5]; // т.е. x = 6
лямда-функции достаточно удобно использовать в qt с их сигналами-слотами. Так вот вместо слота порой удобно использовать ЛФ, что, кстати, и предлагается в некоторых примерах от qt
Anonymous
"Интуитивно понятна" - это inline функция. А нахрена заморачиваться со всякими лямбда - нифига не понятно. Хотя для "говнокода" - в самый раз. Имхо.