Я видел случай, когда лямбда была очень полезна: мой коллега делал код, имеющий миллионы итераций для решения задачи оптимизации пространства. Алгоритм был намного быстрее при использовании лямбды, чем правильная функция! Компилятор Visual C ++ 2013.
sergiol
Ответы:
1491
Проблема
C ++ включает полезные универсальные функции, такие как std::for_eachи std::transform, что может быть очень удобно. К сожалению, они также могут быть довольно громоздкими в использовании, особенно если функтор, который вы хотите применить, уникален для конкретной функции.
#include<algorithm>#include<vector>namespace{struct f {voidoperator()(int){// do something}};}void func(std::vector<int>& v){
f f;
std::for_each(v.begin(), v.end(), f);}
Если вы используете только fодин раз и в этом конкретном месте, кажется излишним писать целый класс, просто чтобы сделать что-то тривиальное и одноразовое.
В C ++ 03 вы можете написать что-то вроде следующего, чтобы сохранить функтор локальным:
void func2(std::vector<int>& v){struct{voidoperator()(int){// do something}} f;
std::for_each(v.begin(), v.end(), f);}
однако это не разрешено, fне может быть передано функции шаблона в C ++ 03.
Новое решение
В C ++ 11 введены лямбда-выражения, позволяющие написать встроенный анонимный функтор для замены struct f. Для небольших простых примеров это может быть чище для чтения (оно хранит все в одном месте) и потенциально проще для поддержки, например, в простейшей форме:
void func3(std::vector<int>& v){
std::for_each(v.begin(), v.end(),[](int){/* do something here*/});}
Лямбда-функции являются просто синтаксическим сахаром для анонимных функторов.
Типы возврата
В простых случаях для вас выводится тип возврата лямбды, например:
void func4(std::vector<double>& v){
std::transform(v.begin(), v.end(), v.begin(),[](double d){return d <0.00001?0: d;});}
однако, когда вы начнете писать более сложные лямбды, вы быстро столкнетесь со случаями, когда компилятор не может определить тип возвращаемого значения, например:
До сих пор мы не использовали ничего, кроме того, что было передано лямбде в нем, но мы также можем использовать другие переменные в лямбде. Если вы хотите получить доступ к другим переменным, вы можете использовать предложение capture (the []of expression), которое до сих пор не использовалось в этих примерах, например:
Вы можете захватить как по ссылке, так и по значению, которое вы можете указать, используя &и =соответственно:
[&epsilon] захватить по ссылке
[&] захватывает все переменные, используемые в лямбда-выражениях по ссылке
[=] захватывает все переменные, используемые в лямбда-выражениях по значению
[&, epsilon] захватывает переменные как с [&], но эпсилон по значению
[=, &epsilon] захватывает переменные как с [=], но эпсилон по ссылке
Сформированное operator()это constпо умолчанию, с подтекстом , что захватывает будет constпри обращении к ним по умолчанию. Это приводит к тому, что каждый вызов с одним и тем же вводом будет давать один и тот же результат, однако вы можете пометить лямбда-mutable запрос как запрос на то, operator()что произведенный вызов не является const.
@ Якк, ты попал в ловушку. Лямбды без перехвата имеют неявное преобразование в указатели типов функций. функция преобразования constвсегда ...
Йоханнес Шауб - Lit
2
@ JohannesSchaub-litb ой подлый - и это происходит, когда вы вызываете ()- он передается как лямбда с нулевым аргументом, но поскольку () constон не соответствует лямбда-выражению, он ищет преобразование типа, которое позволяет это, что включает в себя неявное приведение -to-function-pointer, а затем вызывает это! Подлый!
Якк - Адам Невраумонт
2
Интересно - я изначально думал, что лямбды были анонимными функциями, а не функторами, и был озадачен тем, как работают захваты.
user253751
50
Если вы хотите использовать лямбда-выражения в качестве переменных в своей программе, вы можете использовать: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Но обычно мы позволяем компилятору выводить тип: auto f = [](int a, bool b) -> double { ... }; (и не забывайте #include <functional>)
Evert Heylen
11
Я полагаю, что не все понимают, почему return d < 0.00001 ? 0 : d;гарантированно возвращается double, когда один из операндов является целочисленной константой (это происходит из-за неявного правила продвижения оператора?:, Где 2-й и 3-й операнд сбалансированы друг с другом с помощью обычной арифметики преобразования независимо от того, какой из них будет выбран). Переход на 0.0 : d, возможно, облегчит понимание этого примера.
Лундин
831
Что такое лямбда-функция?
Концепция лямбда-функции в C ++ берет свое начало в лямбда-исчислении и функциональном программировании. Лямбда - это безымянная функция, которая полезна (в реальном программировании, а не в теории) для коротких фрагментов кода, которые невозможно повторно использовать и которые не стоит называть.
В C ++ лямбда-функция определяется следующим образом
[](){}// barebone lambda
или во всей красе
[]()mutable-> T {}// T is the return type, still lacking throw()
[]это список захвата, список ()аргументов и {}тело функции.
Список захвата
Список захвата определяет, что снаружи лямбды должно быть доступно внутри тела функции и как. Это может быть:
значение: [x]
ссылка [& x]
любая переменная в настоящее время в области видимости по ссылке [&]
такой же, как 3, но по значению [=]
Вы можете смешать любое из вышеперечисленного в списке через запятую [x, &y].
Список аргументов
Список аргументов такой же, как и в любой другой функции C ++.
Тело функции
Код, который будет выполнен, когда лямбда будет вызвана.
Возвращаемый тип удержания
Если лямбда имеет только один оператор возврата, тип возврата может быть опущен и имеет неявный тип decltype(return_statement).
изменчивый
Если лямбда помечена как изменяемая (например []() mutable { }), то разрешается изменять значения, которые были захвачены по значению.
Случаи использования
Библиотека, определяемая стандартом ISO, в значительной степени выигрывает от лямбд и повышает удобство использования на несколько тактов, поскольку теперь пользователям не нужно загромождать свой код маленькими функторами в некоторой доступной области видимости.
C ++ 14
В C ++ 14 лямбд были расширены различными предложениями.
Инициализированные лямбда-захваты
Элемент списка захвата теперь можно инициализировать с помощью =. Это позволяет переименовывать переменные и захватывать при перемещении. Пример взят из стандарта:
int x =4;auto y =[&r = x, x = x+1]()->int{
r +=2;return x+2;}();// Updates ::x to 6, and initializes y to 7.
и один взят из Википедии, показывающий, как захватить с помощью std::move:
auto ptr = std::make_unique<int>(10);// See below for std::make_uniqueauto lambda =[ptr = std::move(ptr)]{return*ptr;};
Универсальные лямбды
Теперь лямбда-выражения могут быть универсальными ( autoбыло бы эквивалентно Tздесь, если бы
Tгде-то в окружающем контексте находился аргумент шаблона типа)
auto lambda =[](auto x,auto y){return x + y;};
Улучшено вычитание типа возврата
C ++ 14 позволяет выводить типы возврата для каждой функции и не ограничивает ее функциями формы return expression;. Это также распространяется на лямбды.
В вашем примере для инициализированных лямбда-захватов выше, почему вы заканчиваете функцию lamba с помощью () ;? Это выглядит как [] () {} (); вместо [](){};. Также не должно ли значение х быть 5?
Рамакришнан Каннан
7
@RamakrishnanKannan: 1) функция () должна вызывать лямбду сразу после ее определения и давать y ее возвращаемое значение. Переменная y является целым числом, а не лямбда-выражением. 2) Нет, x = 5 является локальным для лямбды (захват по значению, у которого просто совпадает имя с внешней переменной области видимости x), и затем возвращается x + 2 = 5 + 2. Переназначение внешней переменной x происходит через ссылку r:, r = &x; r += 2;но это происходит с исходным значением 4.
The Vee
168
Лямбда-выражения обычно используются для инкапсуляции алгоритмов, чтобы их можно было передавать в другую функцию. Однако можно выполнить лямбду сразу после определения :
Это делает лямбда-выражения мощным инструментом для рефакторинга сложных функций . Вы начинаете с обертывания секции кода в лямбда-функции, как показано выше. Процесс явной параметризации может затем выполняться постепенно с промежуточным тестированием после каждого шага. После того, как кодовый блок полностью параметризован (как показано при удалении &), вы можете переместить код во внешнее местоположение и сделать его нормальной функцией.
Точно так же вы можете использовать лямбда-выражения для инициализации переменных на основе результата алгоритма ...
int a =[](int b ){int r=1;while(b>0) r*=b--;return r;}(5);// 5!
В качестве способа разделения логики вашей программы вы можете даже найти полезным передать лямбда-выражение в качестве аргумента другому лямбда-выражению ...
Лямбда-выражения также позволяют создавать именованные вложенные функции , что может быть удобным способом избежать дублирования логики. Использование именованных лямбд также немного проще для глаз (по сравнению с анонимными встроенными лямбдами) при передаче нетривиальной функции в качестве параметра другой функции. Примечание: не забывайте точку с запятой после закрывающей фигурной скобки.
auto algorithm =[&](double x,double m,double b )->double{return m*x+b;};int a=algorithm(1,2,3), b=algorithm(4,5,6);
Если последующее профилирование обнаруживает значительные издержки инициализации для объекта функции, вы можете переписать это как обычную функцию.
Вы поняли, что этот вопрос был задан 1,5 года назад и что последнее занятие было почти 1 год назад? В любом случае, вы вносите некоторые интересные идеи, которых я раньше не видел!
Piotr99
7
Спасибо за совет по определению и выполнению! Я думаю, что стоит отметить, что это работает как условие для ifзаявлений: if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespaceпри условии, что iэтоstd::string
Blacklight Shining
74
Таким образом, следующее является юридическим выражением: [](){}();.
Нобар
8
Тьфу! (lambda: None)()Синтаксис Python гораздо более разборчивый.
dan04
9
@nobar - ты прав, я опечатка. Это законно (на этот раз я проверял)main() {{{{((([](){{}}())));}}}}
Марк Лаката,
38
ответы
Q: Что такое лямбда-выражение в C ++ 11?
A: Под капотом это объект автоматически сгенерированного класса с перегрузкой operator () const . Такой объект называется замыканием и создается компилятором. Эта концепция «замыкания» близка к концепции связывания из C ++ 11. Но лямбды обычно генерируют лучший код. И звонки через замыкания позволяют полное встраивание.
Q: Когда я буду использовать один?
A: Чтобы определить "простую и маленькую логику" и попросить компилятор выполнить генерацию из предыдущего вопроса. Вы даете компилятору некоторые выражения, которые вы хотите использовать внутри operator (). Все остальные вещи компилятор сгенерирует вам.
В: Какой класс проблем они решают, что было невозможно до их появления?
A: Это какой-то синтаксический сахар, похожий на перегрузку операторов вместо функций для пользовательских операций добавления, операций с вложенными контактами ... Но это сохраняет больше строк ненужного кода, чтобы обернуть 1-3 строки реальной логики в некоторые классы и т. Д.! Некоторые инженеры считают, что если число строк меньше, то вероятность ошибиться в нем меньше (я тоже так думаю)
Пример использования
auto x =[=](int arg1){printf("%i", arg1);};void(*f)(int)= x;
f(1);
x(1);
Дополнения о лямбдах, не подпадающие под вопрос. Игнорируйте этот раздел, если вы не заинтересованы
1. Захваченные значения. Что вы можете поймать
1.1. Вы можете ссылаться на переменную со статической продолжительностью хранения в лямбдах. Все они захвачены в плен.
1.2. Вы можете использовать лямбду для захвата значений «по значению». В этом случае захваченные переменные будут скопированы в объект функции (замыкание).
[captureVar1,captureVar2](int arg1){}
1.3. Вы можете захватить ссылки. & - в этом контексте означает ссылку, а не указатели.
[&captureVar1,&captureVar2](int arg1){}
1.4. Существует нотация для захвата всех нестатических переменных по значению или по ссылке
[=](int arg1){}// capture all not-static vars by value[&](int arg1){}// capture all not-static vars by reference
1,5. Существует нотация для захвата всех нестатических переменных по значению или по ссылке и указания чего-либо. Больше. Примеры: захват всех нестатических переменных по значению, но по ссылке захват Param2
[=,&Param2](int arg1){}
Захватить все нестатические переменные по ссылке, но по значению Param2
[&,Param2](int arg1){}
2. Возвращаемый тип удержания
2.1. Лямбда-тип возврата может быть выведен, если лямбда-выражение является одним выражением. Или вы можете явно указать это.
Если лямбда имеет более одного выражения, то тип возврата должен быть указан через конечный тип возврата. Кроме того, подобный синтаксис может применяться к автоматическим функциям и функциям-членам.
3. Захваченные значения. То, что вы не можете захватить
3.1. Вы можете захватывать только локальные переменные, а не переменную-член объекта.
4. Конверсии
4.1 !! Лямбда не является указателем на функцию и не является анонимной функцией, но лямбды без захвата можно неявно преобразовать в указатель на функцию.
п.с.
Более подробную информацию о лямбда-грамматике можно найти в рабочем проекте для языка программирования C ++ # 337, 2012-01-16, 5.1.2. Лямбда-выражения, стр.88
В C ++ 14 была добавлена дополнительная функция, названная как «init capture». Позволяет произвольно выполнить декларацию закрытия данных членов:
auto toFloat =[](int value){returnfloat(value);};auto interpolate =[min = toFloat(0), max = toFloat(255)](int value)->float{return(value - min)/(max - min);};
Это [&,=Param2](int arg1){}не похоже на правильный синтаксис. Правильная форма будет[&,Param2](int arg1){}
GetFree
Спасибо. Сначала я попытался скомпилировать этот фрагмент. И кажется странной ассиметрия в допустимых модификаторах в списке захвата // g ++ -std = c ++ 11 main.cpp -o test_bin; ./test_bin #include <stdio.h> int main () {#if 1 {int param = 0; auto f = [=, & param] (int arg1) mutable {param = arg1;}; F (111); printf ("% i \ n", param); } #endif #if 0 {int param = 0; auto f = [&, = param] (int arg1) mutable {param = arg1;}; F (111); printf ("% i \ n", param); } #endif return 0; }
bruziuz
Выглядит, что новая строка не поддерживается в комментарии. Затем я открыл 5.1.2 лямбда-выражения, стр.88, «Рабочий проект, стандарт для языка программирования C ++», номер документа: # 337, 2012-01-16. И посмотрел в грамматический синтаксис. И ты прав. Не существует такой вещи, как захват через "= arg"
bruziuz
Большое спасибо, исправил это в описании, а также получил новые знания по этому поводу.
bruziuz
16
Лямбда-функция - это анонимная функция, которую вы создаете в строке. Он может захватывать переменные, как объяснили некоторые (например, http://www.stroustrup.com/C++11FAQ.html#lambda ), но есть некоторые ограничения. Например, если есть такой интерфейс обратного вызова,
void apply(void(*f)(int)){
f(10);
f(20);
f(30);}
Вы можете написать функцию на месте, чтобы использовать ее так же, как переданную ниже для применения:
int col=0;void output(){
apply([](int data){
cout << data <<((++col %10)?' ':'\n');});}
из-за ограничений в стандарте C ++ 11. Если вы хотите использовать снимки, вы должны полагаться на библиотеку и
#include<functional>
(или какой-то другой библиотеке STL, такой как алгоритм, чтобы получить его косвенно), а затем работать с std :: function вместо передачи нормальных функций в качестве параметров, подобных этому:
причина в том, что лямбда может преобразовываться в указатель на функцию, только если она не имеет перехвата. Если бы applyбыл шаблон, который принимал функтор, он бы работал
sp2danny
1
Но проблема в том, что если apply - это существующий интерфейс, вы не можете позволить себе роскошь объявить его иначе, чем обычная старая функция. Стандарт можно было бы спроектировать так, чтобы каждый раз при запуске такого лямбда-выражения генерировался новый экземпляр простой старой функции с генерируемыми жестко закодированными ссылками на захваченные переменные. Кажется, лямбда-функция генерируется во время компиляции. Есть и другие последствия. Например, если вы объявляете статическую переменную, даже если вы переоцениваете лямбда-выражение, вы не получите новую статическую переменную.
Тед
1
указатель на функцию часто предназначен для сохранения, и захват лямбды может выйти за рамки видимости. что только лямбды без захвата, конвертируемые в указатели функций, были спроектированы
sp2danny
1
Вы все равно должны обратить внимание на то, что переменные стека освобождаются по той же причине в любом случае. См. Blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… Пример, который я написал с output и apply, написан так, что если бы вместо них были разрешены и использовались указатели на функции, они также работали. Col остается распределенным до тех пор, пока не завершатся все вызовы функций из apply. Как бы вы переписали этот код для работы с использованием существующего интерфейса применения? Вы бы в конечном итоге использовали глобальные или статические переменные или более неясное преобразование кода?
Тед
1
или, возможно, вы просто имеете в виду, что лямбда-выражения являются значениями и, следовательно, временными, но код остается постоянным (одноэлементным / статическим), чтобы его можно было вызывать в будущем. В этом случае, возможно, функция должна оставаться распределенной до тех пор, пока ее выделенные стеки захваты остаются распределенными. Конечно, это может привести к беспорядку, если, например, в цикле размещено много вариантов функции.
Тед
12
Одно из лучших объяснений lambda expressionдано автором C ++ Бьярном Страуструпом в его ***The C++ Programming Language***главе 11 книги ( ISBN-13: 978-0321563842 ):
What is a lambda expression?
Лямбда - выражение , иногда также называют лямбда -
функции или (строго говоря , неправильно, но просторечии) как
лямбда , является упрощенной обозначения для определения и с помощью анонимного объекта функции . Вместо определения именованного класса с помощью operator (), создания объекта этого класса и, наконец, его вызова, мы можем использовать сокращение.
When would I use one?
Это особенно полезно, когда мы хотим передать операцию в качестве аргумента алгоритму. В контексте графических пользовательских интерфейсов (и в других местах) такие операции часто называют обратными вызовами .
What class of problem do they solve that wasn't possible prior to their introduction?
Здесь я думаю, что каждое действие, выполненное с помощью лямбда-выражения, может быть решено без них, но с гораздо большим количеством кода и гораздо большей сложностью. Лямбда-выражение - это способ оптимизации вашего кода и способ сделать его более привлекательным. Как грустно от Страуступа:
эффективные способы оптимизации
Some examples
через лямбда-выражение
void print_modulo(constvector<int>& v, ostream& os,int m)// output v[i] to os if v[i]%m==0{
for_each(begin(v),end(v),[&os,m](int x){if(x%m==0) os << x <<'\n';});}
или через функцию
classModulo_print{
ostream& os;// members to hold the capture list int m;public:Modulo_print(ostream& s,int mm):os(s), m(mm){}voidoperator()(int x)const{if(x%m==0) os << x <<'\n';}};
или даже
void print_modulo(constvector<int>& v, ostream& os,int m)// output v[i] to os if v[i]%m==0{classModulo_print{
ostream& os;// members to hold the capture listint m;public:Modulo_print(ostream& s,int mm):os(s), m(mm){}voidoperator()(int x)const{if(x%m==0) os << x <<'\n';}};
for_each(begin(v),end(v),Modulo_print{os,m});}
Если вам нужно, вы можете назвать lambda expressionкак ниже:
void print_modulo(constvector<int>& v, ostream& os,int m)// output v[i] to os if v[i]%m==0{autoModulo_print=[&os,m](int x){if(x%m==0) os << x <<'\n';};
for_each(begin(v),end(v),Modulo_print);}
Или предположим другой простой пример
voidTestFunctions::simpleLambda(){bool sensitive =true;
std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});
sort(v.begin(),v.end(),[sensitive](int x,int y){
printf("\n%i\n", x < y);return sensitive ? x < y : abs(x)< abs(y);});
printf("sorted");
for_each(v.begin(), v.end(),[](int x){
printf("x - %i;", x);});}
будет генерировать следующий
0
1
0
1
0
1
0
1
0
1
0 отсортировано - 1; х - 3; х - 4; х - 5; х - 6; х - 7; х - 33;
[]- это список захвата или lambda introducer: если не lambdasтребуется доступ к их локальной среде, мы можем его использовать.
Цитата из книги:
Первым символом лямбда-выражения всегда является [ . Лямбда-интродьюсер может принимать различные формы:
• [] : пустой список захвата. Это подразумевает, что никакие локальные имена из окружающего контекста не могут использоваться в лямбда-теле. Для таких лямбда-выражений данные получают из аргументов или из нелокальных переменных.
• [&] : неявный захват по ссылке. Все местные имена могут быть использованы. Все локальные переменные доступны по ссылке.
• [=] : неявный захват по значению. Все местные имена могут быть использованы. Все имена относятся к копиям локальных переменных, взятых в точке вызова лямбда-выражения.
• [capture-list]: явный захват; список захвата - это список имен локальных переменных, которые должны быть захвачены (т.е. сохранены в объекте) по ссылке или по значению. Переменные с именами, начинающимися с &, фиксируются по ссылке. Другие переменные фиксируются по значению. Список захвата может также содержать это и имена, сопровождаемые ... как элементы.
• [&, capture-list] : неявно захватывает по ссылке все локальные переменные с именами, не упомянутыми в списке. Список захвата может содержать это. Перечисленным именам не может предшествовать &. Переменные, названные в списке захвата, фиксируются по значению.
• [=, capture-list] : неявно захватывать по значению все локальные переменные с именами, не упомянутыми в списке. Список захвата не может содержать это. Перечисленным именам должен предшествовать &. Переменные, названные в списке захвата, захватываются по ссылке.
Обратите внимание, что локальное имя, которому предшествует &, всегда захватывается ссылкой, а локальное имя, которому не предшествует &, всегда захватывается значением. Только захват по ссылке позволяет модифицировать переменные в вызывающей среде.
Хорошее объяснение. Используя основанные на диапазоне для циклов, вы можете избежать лямбд и сократить кодfor (int x : v) { if (x % m == 0) os << x << '\n';}
Дитрих Баумгартен
2
Ну, одно практическое применение, которое я обнаружил, - это уменьшение кода котельной плиты. Например:
void process_z_vec(vector<int>& vec){auto print_2d =[](constvector<int>& board,int bsize){for(int i =0; i<bsize; i++){for(int j=0; j<bsize; j++){
cout << board[bsize*i+j]<<" ";}
cout <<"\n";}};// Do sth with the vec.
print_2d(vec,x_size);// Do sth else with the vec.
print_2d(vec,y_size);//... }
Без лямбды может потребоваться что-то сделать для разных bsizeслучаев. Конечно, вы могли бы создать функцию, но что, если вы хотите ограничить использование в рамках пользовательской функции Soul? природа лямбда выполняет это требование, и я использую его для этого случая.
Лямбда в c ++ обрабатывается как «функция, доступная на ходу». да, буквально на ходу, вы определяете это; используй это; и когда родительская функция завершает область действия, лямбда-функция исчезает.
я опишу, чего нет, но важно знать каждому программисту на c ++
Лямбда не предназначена для использования везде, и каждая функция не может быть заменена лямбда. Это также не самый быстрый по сравнению с обычной функцией. потому что у него есть некоторые накладные расходы, которые должны обрабатываться лямбда-выражением.
это, безусловно, поможет в некоторых случаях сократить количество строк. он может быть в основном использован для раздела кода, который вызывается в одной и той же функции один или несколько раз, и этот фрагмент кода больше нигде не нужен, так что вы можете создать для него отдельную функцию.
Ниже приведен основной пример лямбды и что происходит в фоновом режиме.
int main(){int member =10;class __lambda_6_18
{int member;public:inline/*constexpr */intoperator()(int a,int b)const{return a + b + member;}public: __lambda_6_18(int _member): member{_member}{}};
__lambda_6_18 endGame = __lambda_6_18{member};
endGame.operator()(4,5);return0;}
так что, как вы можете видеть, какие накладные расходы добавляются при использовании. так что не стоит использовать их везде. это может использоваться в местах, где они применимы.
да, буквально на ходу, вы определяете это; используй это; и когда родительская функция завершает область действия, лямбда-функция исчезла ... что если функция возвращает лямбда-функцию вызывающей стороне?
Наваз
1
Это также не самый быстрый по сравнению с обычной функцией. потому что у него есть некоторые накладные расходы, которые должны обрабатываться лямбда-выражением. Вы когда - нибудь на самом деле запустить любой тест , чтобы поддержать это требование ? Напротив, шаблоны lambda + часто генерируют максимально быстрый код.
Вы можете инициализировать константный член вашего класса с помощью вызова функции, которая устанавливает его значение, возвращая его вывод в качестве выходного параметра.
Ответы:
Проблема
C ++ включает полезные универсальные функции, такие как
std::for_each
иstd::transform
, что может быть очень удобно. К сожалению, они также могут быть довольно громоздкими в использовании, особенно если функтор, который вы хотите применить, уникален для конкретной функции.Если вы используете только
f
один раз и в этом конкретном месте, кажется излишним писать целый класс, просто чтобы сделать что-то тривиальное и одноразовое.В C ++ 03 вы можете написать что-то вроде следующего, чтобы сохранить функтор локальным:
однако это не разрешено,
f
не может быть передано функции шаблона в C ++ 03.Новое решение
В C ++ 11 введены лямбда-выражения, позволяющие написать встроенный анонимный функтор для замены
struct f
. Для небольших простых примеров это может быть чище для чтения (оно хранит все в одном месте) и потенциально проще для поддержки, например, в простейшей форме:Лямбда-функции являются просто синтаксическим сахаром для анонимных функторов.
Типы возврата
В простых случаях для вас выводится тип возврата лямбды, например:
однако, когда вы начнете писать более сложные лямбды, вы быстро столкнетесь со случаями, когда компилятор не может определить тип возвращаемого значения, например:
Чтобы решить эту проблему, вы можете явно указать тип возвращаемого значения для лямбда-функции, используя
-> T
:«Захват» переменных
До сих пор мы не использовали ничего, кроме того, что было передано лямбде в нем, но мы также можем использовать другие переменные в лямбде. Если вы хотите получить доступ к другим переменным, вы можете использовать предложение capture (the
[]
of expression), которое до сих пор не использовалось в этих примерах, например:Вы можете захватить как по ссылке, так и по значению, которое вы можете указать, используя
&
и=
соответственно:[&epsilon]
захватить по ссылке[&]
захватывает все переменные, используемые в лямбда-выражениях по ссылке[=]
захватывает все переменные, используемые в лямбда-выражениях по значению[&, epsilon]
захватывает переменные как с [&], но эпсилон по значению[=, &epsilon]
захватывает переменные как с [=], но эпсилон по ссылкеСформированное
operator()
этоconst
по умолчанию, с подтекстом , что захватывает будетconst
при обращении к ним по умолчанию. Это приводит к тому, что каждый вызов с одним и тем же вводом будет давать один и тот же результат, однако вы можете пометить лямбда-mutable
запрос как запрос на то,operator()
что произведенный вызов не являетсяconst
.источник
const
всегда ...()
- он передается как лямбда с нулевым аргументом, но поскольку() const
он не соответствует лямбда-выражению, он ищет преобразование типа, которое позволяет это, что включает в себя неявное приведение -to-function-pointer, а затем вызывает это! Подлый!std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };
Но обычно мы позволяем компилятору выводить тип:auto f = [](int a, bool b) -> double { ... };
(и не забывайте#include <functional>
)return d < 0.00001 ? 0 : d;
гарантированно возвращается double, когда один из операндов является целочисленной константой (это происходит из-за неявного правила продвижения оператора?:, Где 2-й и 3-й операнд сбалансированы друг с другом с помощью обычной арифметики преобразования независимо от того, какой из них будет выбран). Переход на0.0 : d
, возможно, облегчит понимание этого примера.Что такое лямбда-функция?
Концепция лямбда-функции в C ++ берет свое начало в лямбда-исчислении и функциональном программировании. Лямбда - это безымянная функция, которая полезна (в реальном программировании, а не в теории) для коротких фрагментов кода, которые невозможно повторно использовать и которые не стоит называть.
В C ++ лямбда-функция определяется следующим образом
или во всей красе
[]
это список захвата, список()
аргументов и{}
тело функции.Список захвата
Список захвата определяет, что снаружи лямбды должно быть доступно внутри тела функции и как. Это может быть:
Вы можете смешать любое из вышеперечисленного в списке через запятую
[x, &y]
.Список аргументов
Список аргументов такой же, как и в любой другой функции C ++.
Тело функции
Код, который будет выполнен, когда лямбда будет вызвана.
Возвращаемый тип удержания
Если лямбда имеет только один оператор возврата, тип возврата может быть опущен и имеет неявный тип
decltype(return_statement)
.изменчивый
Если лямбда помечена как изменяемая (например
[]() mutable { }
), то разрешается изменять значения, которые были захвачены по значению.Случаи использования
Библиотека, определяемая стандартом ISO, в значительной степени выигрывает от лямбд и повышает удобство использования на несколько тактов, поскольку теперь пользователям не нужно загромождать свой код маленькими функторами в некоторой доступной области видимости.
C ++ 14
В C ++ 14 лямбд были расширены различными предложениями.
Инициализированные лямбда-захваты
Элемент списка захвата теперь можно инициализировать с помощью
=
. Это позволяет переименовывать переменные и захватывать при перемещении. Пример взят из стандарта:и один взят из Википедии, показывающий, как захватить с помощью
std::move
:Универсальные лямбды
Теперь лямбда-выражения могут быть универсальными (
auto
было бы эквивалентноT
здесь, если быT
где-то в окружающем контексте находился аргумент шаблона типа)Улучшено вычитание типа возврата
C ++ 14 позволяет выводить типы возврата для каждой функции и не ограничивает ее функциями формы
return expression;
. Это также распространяется на лямбды.источник
r = &x; r += 2;
но это происходит с исходным значением 4.Лямбда-выражения обычно используются для инкапсуляции алгоритмов, чтобы их можно было передавать в другую функцию. Однако можно выполнить лямбду сразу после определения :
функционально эквивалентно
Это делает лямбда-выражения мощным инструментом для рефакторинга сложных функций . Вы начинаете с обертывания секции кода в лямбда-функции, как показано выше. Процесс явной параметризации может затем выполняться постепенно с промежуточным тестированием после каждого шага. После того, как кодовый блок полностью параметризован (как показано при удалении
&
), вы можете переместить код во внешнее местоположение и сделать его нормальной функцией.Точно так же вы можете использовать лямбда-выражения для инициализации переменных на основе результата алгоритма ...
В качестве способа разделения логики вашей программы вы можете даже найти полезным передать лямбда-выражение в качестве аргумента другому лямбда-выражению ...
Лямбда-выражения также позволяют создавать именованные вложенные функции , что может быть удобным способом избежать дублирования логики. Использование именованных лямбд также немного проще для глаз (по сравнению с анонимными встроенными лямбдами) при передаче нетривиальной функции в качестве параметра другой функции. Примечание: не забывайте точку с запятой после закрывающей фигурной скобки.
Если последующее профилирование обнаруживает значительные издержки инициализации для объекта функции, вы можете переписать это как обычную функцию.
источник
if
заявлений:if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace
при условии, чтоi
этоstd::string
[](){}();
.(lambda: None)()
Синтаксис Python гораздо более разборчивый.main() {{{{((([](){{}}())));}}}}
ответы
Q: Что такое лямбда-выражение в C ++ 11?
A: Под капотом это объект автоматически сгенерированного класса с перегрузкой operator () const . Такой объект называется замыканием и создается компилятором. Эта концепция «замыкания» близка к концепции связывания из C ++ 11. Но лямбды обычно генерируют лучший код. И звонки через замыкания позволяют полное встраивание.
Q: Когда я буду использовать один?
A: Чтобы определить "простую и маленькую логику" и попросить компилятор выполнить генерацию из предыдущего вопроса. Вы даете компилятору некоторые выражения, которые вы хотите использовать внутри operator (). Все остальные вещи компилятор сгенерирует вам.
В: Какой класс проблем они решают, что было невозможно до их появления?
A: Это какой-то синтаксический сахар, похожий на перегрузку операторов вместо функций для пользовательских операций добавления, операций с вложенными контактами ... Но это сохраняет больше строк ненужного кода, чтобы обернуть 1-3 строки реальной логики в некоторые классы и т. Д.! Некоторые инженеры считают, что если число строк меньше, то вероятность ошибиться в нем меньше (я тоже так думаю)
Пример использования
Дополнения о лямбдах, не подпадающие под вопрос. Игнорируйте этот раздел, если вы не заинтересованы
1. Захваченные значения. Что вы можете поймать
1.1. Вы можете ссылаться на переменную со статической продолжительностью хранения в лямбдах. Все они захвачены в плен.
1.2. Вы можете использовать лямбду для захвата значений «по значению». В этом случае захваченные переменные будут скопированы в объект функции (замыкание).
1.3. Вы можете захватить ссылки. & - в этом контексте означает ссылку, а не указатели.
1.4. Существует нотация для захвата всех нестатических переменных по значению или по ссылке
1,5. Существует нотация для захвата всех нестатических переменных по значению или по ссылке и указания чего-либо. Больше. Примеры: захват всех нестатических переменных по значению, но по ссылке захват Param2
Захватить все нестатические переменные по ссылке, но по значению Param2
2. Возвращаемый тип удержания
2.1. Лямбда-тип возврата может быть выведен, если лямбда-выражение является одним выражением. Или вы можете явно указать это.
Если лямбда имеет более одного выражения, то тип возврата должен быть указан через конечный тип возврата. Кроме того, подобный синтаксис может применяться к автоматическим функциям и функциям-членам.
3. Захваченные значения. То, что вы не можете захватить
3.1. Вы можете захватывать только локальные переменные, а не переменную-член объекта.
4. Конверсии
4.1 !! Лямбда не является указателем на функцию и не является анонимной функцией, но лямбды без захвата можно неявно преобразовать в указатель на функцию.
п.с.
Более подробную информацию о лямбда-грамматике можно найти в рабочем проекте для языка программирования C ++ # 337, 2012-01-16, 5.1.2. Лямбда-выражения, стр.88
В C ++ 14 была добавлена дополнительная функция, названная как «init capture». Позволяет произвольно выполнить декларацию закрытия данных членов:
источник
[&,=Param2](int arg1){}
не похоже на правильный синтаксис. Правильная форма будет[&,Param2](int arg1){}
Лямбда-функция - это анонимная функция, которую вы создаете в строке. Он может захватывать переменные, как объяснили некоторые (например, http://www.stroustrup.com/C++11FAQ.html#lambda ), но есть некоторые ограничения. Например, если есть такой интерфейс обратного вызова,
Вы можете написать функцию на месте, чтобы использовать ее так же, как переданную ниже для применения:
Но вы не можете сделать это:
из-за ограничений в стандарте C ++ 11. Если вы хотите использовать снимки, вы должны полагаться на библиотеку и
(или какой-то другой библиотеке STL, такой как алгоритм, чтобы получить его косвенно), а затем работать с std :: function вместо передачи нормальных функций в качестве параметров, подобных этому:
источник
apply
был шаблон, который принимал функтор, он бы работалОдно из лучших объяснений
lambda expression
дано автором C ++ Бьярном Страуструпом в его***The C++ Programming Language***
главе 11 книги ( ISBN-13: 978-0321563842 ):What is a lambda expression?
When would I use one?
What class of problem do they solve that wasn't possible prior to their introduction?
Здесь я думаю, что каждое действие, выполненное с помощью лямбда-выражения, может быть решено без них, но с гораздо большим количеством кода и гораздо большей сложностью. Лямбда-выражение - это способ оптимизации вашего кода и способ сделать его более привлекательным. Как грустно от Страуступа:
Some examples
через лямбда-выражение
или через функцию
или даже
Если вам нужно, вы можете назвать
lambda expression
как ниже:Или предположим другой простой пример
будет генерировать следующий
[]
- это список захвата илиlambda introducer
: если неlambdas
требуется доступ к их локальной среде, мы можем его использовать.Цитата из книги:
Additional
Lambda expression
форматДополнительные ссылки:
источник
for (int x : v) { if (x % m == 0) os << x << '\n';}
Ну, одно практическое применение, которое я обнаружил, - это уменьшение кода котельной плиты. Например:
Без лямбды может потребоваться что-то сделать для разных
bsize
случаев. Конечно, вы могли бы создать функцию, но что, если вы хотите ограничить использование в рамках пользовательской функции Soul? природа лямбда выполняет это требование, и я использую его для этого случая.источник
Лямбда в c ++ обрабатывается как «функция, доступная на ходу». да, буквально на ходу, вы определяете это; используй это; и когда родительская функция завершает область действия, лямбда-функция исчезает.
C ++ представил его в C ++ 11, и все начали использовать его, как и везде. пример и что такое лямбда можно найти здесь https://en.cppreference.com/w/cpp/language/lambda
я опишу, чего нет, но важно знать каждому программисту на c ++
Лямбда не предназначена для использования везде, и каждая функция не может быть заменена лямбда. Это также не самый быстрый по сравнению с обычной функцией. потому что у него есть некоторые накладные расходы, которые должны обрабатываться лямбда-выражением.
это, безусловно, поможет в некоторых случаях сократить количество строк. он может быть в основном использован для раздела кода, который вызывается в одной и той же функции один или несколько раз, и этот фрагмент кода больше нигде не нужен, так что вы можете создать для него отдельную функцию.
Ниже приведен основной пример лямбды и что происходит в фоновом режиме.
Код пользователя:
Как компиляция расширяет это:
так что, как вы можете видеть, какие накладные расходы добавляются при использовании. так что не стоит использовать их везде. это может использоваться в местах, где они применимы.
источник
Решается одна проблема: код, более простой, чем лямбда, для вызова в конструкторе, который использует функцию выходного параметра для инициализации константного члена
Вы можете инициализировать константный член вашего класса с помощью вызова функции, которая устанавливает его значение, возвращая его вывод в качестве выходного параметра.
источник