Widziałem przypadek, w którym lambda była bardzo przydatna: mój kolega tworzył kod, który ma miliony iteracji, aby rozwiązać problem optymalizacji przestrzeni. Algorytm był znacznie szybszy przy użyciu lambda niż właściwej funkcji! Kompilatorem jest Visual C ++ 2013.
sergiol
Odpowiedzi:
1489
Problem
C ++ zawiera przydatne funkcje ogólne, takie jak std::for_eachi std::transform, które mogą być bardzo przydatne. Niestety mogą być również uciążliwe w użyciu, szczególnie jeśli funktor , który chcesz zastosować, jest unikalny dla konkretnej funkcji.
#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);}
Jeśli użyjesz tylko fraz i w tym konkretnym miejscu, wydaje się przesadą pisać całą klasę, aby zrobić coś trywialnego i jednorazowego.
W C ++ 03 możesz mieć ochotę napisać coś takiego, aby utrzymać funktor lokalny:
void func2(std::vector<int>& v){struct{voidoperator()(int){// do something}} f;
std::for_each(v.begin(), v.end(), f);}
jednak nie jest to dozwolone, fnie można go przekazać do funkcji szablonu w C ++ 03.
Nowe rozwiązanie
C ++ 11 wprowadza lambdas, które pozwalają napisać wbudowany, anonimowy funktor, który zastąpi struct f. W przypadku małych prostych przykładów może to być łatwiejsze do odczytania (utrzymuje wszystko w jednym miejscu) i potencjalnie prostsze w utrzymaniu, na przykład w najprostszej formie:
void func3(std::vector<int>& v){
std::for_each(v.begin(), v.end(),[](int){/* do something here*/});}
Funkcje lambda to po prostu cukier syntaktyczny dla anonimowych funktorów.
Typy zwrotów
W prostych przypadkach obliczany jest typ zwrotu lambda, np .:
void func4(std::vector<double>& v){
std::transform(v.begin(), v.end(), v.begin(),[](double d){return d <0.00001?0: d;});}
jednak kiedy zaczniesz pisać bardziej złożone lambdy, szybko napotkasz przypadki, w których kompilator nie może wydedukować typu zwrotu, np .:
Do tej pory nie używaliśmy niczego innego niż to, co zostało przekazane do niej w lambdzie, ale możemy również używać innych zmiennych w obrębie lambda. Jeśli chcesz uzyskać dostęp do innych zmiennych, możesz użyć klauzuli przechwytywania ( []wyrażenia), która do tej pory nie była używana w tych przykładach, np .:
Możesz przechwytywać zarówno według odwołania, jak i wartości, które możesz określić za pomocą &i =odpowiednio:
[&epsilon] przechwytywanie przez odniesienie
[&] przechwytuje wszystkie zmienne używane w lambda przez odniesienie
[=] przechwytuje wszystkie zmienne używane w lambda według wartości
[&, epsilon] przechwytuje zmienne takie jak z [&], ale epsilon według wartości
[=, &epsilon] przechwytuje zmienne jak z [=], ale epsilon przez odniesienie
Generowane operator()jest constdomyślnie, z implikacją, że przechwytywanie nastąpi constpo domyślnym dostępie do nich. Powoduje to, że każde wywołanie z tym samym wejściem daje ten sam wynik, jednak można oznaczyć lambda jakomutable żądanie, operator()aby nie zostało wygenerowane const.
@Yakk zostałeś uwięziony. lambd bez przechwytywania mają niejawną konwersję do wskaźników typu funkcji. funkcja konwersji jest constzawsze ...
Johannes Schaub - litb
2
@ JohannesSchaub-litb oh podstępny - i zdarza się, gdy wywołujesz ()- jest przekazywany jako lambda z zerowym argumentem, ale ponieważ () constnie pasuje do lambda, szuka konwersji typu, która na to pozwala, w tym niejawnego rzutowania -do-funkcji-wskaźnik, a następnie wywołuje to! Podstępny!
Yakk - Adam Nevraumont
2
Ciekawe - początkowo myślałem, że lambdas są anonimowymi funkcjami, a nie funktorami, i byłem zdezorientowany, jak działają przechwytywania.
user253751
49
Jeśli chcesz używać lambdas jako zmiennych w swoim programie, możesz użyć: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Ale zwykle pozwalamy kompilatorowi wydedukować typ: auto f = [](int a, bool b) -> double { ... }; (i nie zapomnij #include <functional>)
Evert Heylen
11
Przypuszczam, że nie wszyscy rozumieją, dlaczego return d < 0.00001 ? 0 : d;gwarantowany jest zwrot podwójny, gdy jeden z operandów jest stałą całkowitą (wynika to z domyślnej reguły promocji operatora?: Operator, w której drugi i trzeci operand są równoważone względem siebie za pomocą zwykłej arytmetyki konwersje bez względu na to, który zostanie wybrany). Zmiana na 0.0 : dbyć może ułatwiłaby zrozumienie przykładu.
Lundin,
828
Co to jest funkcja lambda?
Koncepcja C ++ funkcji lambda wywodzi się z rachunku lambda i programowania funkcjonalnego. Lambda jest nienazwaną funkcją przydatną (w rzeczywistym programowaniu, nie w teorii) dla krótkich fragmentów kodu, których nie można ponownie użyć i których nie warto nazywać.
W C ++ funkcja lambda jest zdefiniowana w ten sposób
[](){}// barebone lambda
lub w całej okazałości
[]()mutable-> T {}// T is the return type, still lacking throw()
[]to lista przechwytywania, lista ()argumentów i {}treść funkcji.
Lista przechwytywania
Lista przechwytywania określa, co z zewnątrz lambda powinno być dostępne wewnątrz ciała funkcji i jak. Może to być:
wartość: [x]
referencja [i x]
dowolna zmienna obecnie objęta zakresem przez odniesienie [&]
taki sam jak 3, ale według wartości [=]
Możesz mieszać dowolne z powyższych elementów na liście oddzielonej przecinkami [x, &y].
Lista argumentów
Lista argumentów jest taka sama jak w każdej innej funkcji C ++.
Ciało funkcji
Kod, który zostanie wykonany po wywołaniu lambda.
Odliczenie typu zwrotu
Jeśli lambda ma tylko jedną instrukcję return, typ zwracany można pominąć i ma domyślny typ decltype(return_statement).
Zmienny
Jeśli lambda jest oznaczona jako zmienna (np. []() mutable { }), Można mutować wartości, które zostały przechwycone przez wartość.
Przypadków użycia
Biblioteka zdefiniowana w standardzie ISO znacznie korzysta z lambdas i podnosi użyteczność kilku pasków, ponieważ teraz użytkownicy nie muszą zaśmiecać kodu małymi funktorami w dostępnym zakresie.
C ++ 14
W C ++ 14 lambdów zostało poszerzonych o różne propozycje.
Zainicjowano przechwyty lambda
Teraz można zainicjować element listy przechwytywania =. Pozwala to na zmianę nazw zmiennych i przechwytywanie przez przenoszenie. Przykład wzięty ze standardu:
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.
i jeden wzięty z Wikipedii pokazujący, jak robić zdjęcia std::move:
auto ptr = std::make_unique<int>(10);// See below for std::make_uniqueauto lambda =[ptr = std::move(ptr)]{return*ptr;};
Ogólne Lambdas
Lambdas mogą być teraz ogólne ( autobyłoby to równoważne z Ttym, gdyby
Tgdzieś w otaczającym zakresie był argument szablonu typu):
auto lambda =[](auto x,auto y){return x + y;};
Ulepszone odliczenie typu zwrotu
C ++ 14 pozwala przewidywać typy zwrotów dla każdej funkcji i nie ogranicza jej do funkcji formularza return expression;. Dotyczy to również lambd.
W powyższym przykładzie zainicjowanych zrzutów lambda, dlaczego kończysz funkcję Lamba za pomocą () ;? Wygląda to jak [] () {} (); zamiast [](){};. Czy wartość x również nie powinna wynosić 5?
Ramakrishnan Kannan
6
@RamakrishnanKannan: 1) () są tam, aby wywołać lambda zaraz po zdefiniowaniu i podać jej wartość zwracaną. Zmienna y jest liczbą całkowitą, a nie lambda. 2) Nie, x = 5 jest lokalne dla lambda (przechwytywanie według wartości, które akurat ma taką samą nazwę jak zmienna zakresu zewnętrznego x), a następnie zwracane jest x + 2 = 5 + 2. Ponowne przypisanie zmiennej zewnętrznej x odbywa się poprzez odniesienie r:, r = &x; r += 2;ale dzieje się tak z pierwotną wartością 4.
The Vee
167
Wyrażenia lambda są zwykle używane do enkapsulacji algorytmów, aby można je było przekazać do innej funkcji. Jednak możliwe jest natychmiastowe wykonanie lambda od definicji :
To sprawia, że wyrażenia lambda są potężnym narzędziem do refaktoryzacji złożonych funkcji . Zaczynasz od zawijania sekcji kodu w funkcji lambda, jak pokazano powyżej. Proces jawnej parametryzacji można następnie przeprowadzać stopniowo za pomocą testów pośrednich po każdym kroku. Po pełnym sparametryzowaniu bloku kodu (jak pokazano po usunięciu &), możesz przenieść kod do zewnętrznego miejsca i ustawić go jako normalną funkcję.
Podobnie możesz użyć wyrażeń lambda do zainicjowania zmiennych na podstawie wyniku algorytmu ...
int a =[](int b ){int r=1;while(b>0) r*=b--;return r;}(5);// 5!
Jako sposób na podzielenie logiki programu może okazać się przydatne przekazanie wyrażenia lambda jako argumentu do innego wyrażenia lambda ...
Wyrażenia lambda pozwalają również tworzyć nazwane funkcje zagnieżdżone , co może być wygodnym sposobem uniknięcia podwójnej logiki. Używanie nazwanych lambdas jest również nieco łatwiejsze dla oczu (w porównaniu do anonimowych lambdów wbudowanych), gdy przekazuje się nietrywialną funkcję jako parametr do innej funkcji. Uwaga: nie zapomnij o średniku po zamykającym nawiasie klamrowym.
auto algorithm =[&](double x,double m,double b )->double{return m*x+b;};int a=algorithm(1,2,3), b=algorithm(4,5,6);
Jeśli kolejne profilowanie ujawni znaczny narzut związany z inicjalizacją dla obiektu funkcji, możesz przepisać to jako normalną funkcję.
Czy zdałeś sobie sprawę, że to pytanie zostało zadane 1,5 roku temu, a ostatnia aktywność miała miejsce prawie rok temu? W każdym razie wnosisz ciekawe pomysły, których wcześniej nie widziałem!
Piotr99
7
Dzięki za jednoczesną wskazówkę „zdefiniuj i wykonaj”! Myślę, że warto zauważyć, że działa to jako kontrowersja dla ifoświadczeń: if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespacezakładając, że ijeststd::string
Blacklight Shining
74
Więc po to wyraz prawny: [](){}();.
nobar
8
Ugh! (lambda: None)()Składnia Pythona jest o wiele bardziej czytelna.
dan04
9
@nobar - masz rację, pomyliłem się. To jest legalne (tym razem przetestowałem)main() {{{{((([](){{}}())));}}}}
Mark Lakata
38
Odpowiedzi
P: Co to jest wyrażenie lambda w C ++ 11?
Odp .: Pod maską jest to obiekt klasy generowanej automatycznie z przeciążeniem operatora () const . Taki obiekt nazywa się zamknięciem i jest tworzony przez kompilator. Ta koncepcja „zamknięcia” jest zbliżona do koncepcji wiązania z C ++ 11. Ale lambda zazwyczaj generują lepszy kod. Połączenia przez zamknięcia umożliwiają pełne wstawianie.
P: Kiedy miałbym go użyć?
Odp .: Aby zdefiniować „prostą i małą logikę” i poprosić kompilatora o wykonanie generacji z poprzedniego pytania. Dajesz kompilatorowi kilka wyrażeń, które mają znajdować się wewnątrz operatora (). Wszystkie inne kompilatory rzeczy wygenerują dla Ciebie.
P: Jakiego rodzaju problemy rozwiązują, które nie były możliwe przed ich wprowadzeniem?
Odp .: Jest to rodzaj cukru składniowego, takiego jak przeciążanie operatorów zamiast funkcji do niestandardowych operacji dodawania, subrtact ... Zapisuje jednak więcej wierszy niepotrzebnego kodu, aby zawrzeć 1-3 linie prawdziwej logiki w niektórych klasach itp.! Niektórzy inżynierowie uważają, że jeśli liczba wierszy jest mniejsza, wówczas istnieje mniejsze prawdopodobieństwo popełnienia błędów (tak też uważam)
Przykład użycia
auto x =[=](int arg1){printf("%i", arg1);};void(*f)(int)= x;
f(1);
x(1);
Dodatki dotyczące lambdas, nie objęte pytaniem. Zignoruj tę sekcję, jeśli nie jesteś zainteresowany
1. Przechwycone wartości. Co możesz uchwycić
1.1 Możesz odwoływać się do zmiennej o statycznym czasie przechowywania w lambdas. Wszyscy zostali schwytani.
1.2 Możesz użyć lambda do przechwytywania wartości „według wartości”. W takim przypadku przechwycone zmienne zostaną skopiowane do obiektu funkcji (zamknięcie).
[captureVar1,captureVar2](int arg1){}
1.3 Możesz przechwycić jako odniesienie. & - w tym kontekście oznacza odniesienie, a nie wskaźniki.
[&captureVar1,&captureVar2](int arg1){}
1.4 Istnieje notacja do przechwytywania wszystkich zmiennych niestatycznych według wartości lub referencji
[=](int arg1){}// capture all not-static vars by value[&](int arg1){}// capture all not-static vars by reference
1.5 Istnieje notacja do przechwytywania wszystkich zmiennych niestatycznych według wartości lub referencji i określania smth. więcej. Przykłady: Przechwyć wszystkie zmienne statyczne według wartości, ale poprzez odniesienie przechwyć Param2
[=,&Param2](int arg1){}
Przechwyć wszystkie zmienne statyczne przez odniesienie, ale przez przechwycenie wartości Param2
[&,Param2](int arg1){}
2. Odliczenie typu zwrotu
2.1 Typ powrotu lambda można wywnioskować, jeśli lambda jest jednym wyrażeniem. Lub możesz to wyraźnie określić.
Jeśli lambda ma więcej niż jedno wyrażenie, to typ zwracany musi być określony za pomocą końcowego typu zwracanego. Podobną składnię można również zastosować do funkcji automatycznych i funkcji składowych
3. Przechwycone wartości. Czego nie można uchwycić
3.1 Można przechwytywać tylko zmienne lokalne, a nie zmienną składową obiektu.
4. Konwersje
4.1 !! Lambda nie jest wskaźnikiem funkcji i nie jest funkcją anonimową, ale lambdy bez przechwytywania można pośrednio przekonwertować na wskaźnik funkcji.
ps
Więcej informacji na temat gramatyki lambda można znaleźć w Roboczym szkicu dla języka programowania C ++ # 337, 16.01.2012, 5.1.2. Wyrażenia lambda, s. 88
W C ++ 14 dodano dodatkową funkcję o nazwie „przechwytywanie init”. Pozwala na arbitrażowe zgłaszanie członków danych zamknięcia:
auto toFloat =[](int value){returnfloat(value);};auto interpolate =[min = toFloat(0), max = toFloat(255)](int value)->float{return(value - min)/(max - min);};
To [&,=Param2](int arg1){}nie wydaje się poprawna składnia. Prawidłowa forma to[&,Param2](int arg1){}
GetFree
Dzięki. Najpierw próbowałem skompilować ten fragment kodu. I wydaje się dziwna asymetria w dozwolonych modyfikatorach na liście przechwytywania // 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
Wygląda na to, że nowa linia nie jest obsługiwana w komentarzu. Następnie otworzyłem wyrażenia Lambda 5.1.2, s. 88, „Szkic roboczy, standard dla języka programowania C ++”, numer dokumentu: # 337, 16.01.2012. I przyjrzał się składni gramatycznej. I masz rację. Nie ma czegoś takiego jak przechwytywanie za pomocą „= arg”
bruziuz 16.04.17
Wielkie dzięki, naprawiłem to w opisie, a także zdobyłem nową wiedzę na ten temat.
bruziuz 16.04.17
16
Funkcja lambda to anonimowa funkcja tworzona bezpośrednio. Może przechwytywać zmienne, jak niektórzy wyjaśnili (np. Http://www.stroustrup.com/C++11FAQ.html#lambda ), ale istnieją pewne ograniczenia. Na przykład jeśli istnieje taki interfejs zwrotny,
void apply(void(*f)(int)){
f(10);
f(20);
f(30);}
możesz napisać funkcję na miejscu, aby użyć jej tak jak tej, którą zastosowano poniżej:
int col=0;void output(){
apply([](int data){
cout << data <<((++col %10)?' ':'\n');});}
z powodu ograniczeń w standardzie C ++ 11. Jeśli chcesz używać przechwytywania, musisz polegać na bibliotece i
#include<functional>
(lub inny algorytm podobny do biblioteki STL, aby uzyskać go pośrednio), a następnie pracować ze std :: function zamiast przekazywać normalne funkcje jako parametry takie jak to:
Powodem jest to, że lambda może przekształcić się we wskaźnik funkcji tylko wtedy, gdy nie ma przechwytywania. gdyby applybył szablonem, który akceptuje funktor, działałby
sp2danny,
1
Problem polega jednak na tym, że jeśli Apply jest istniejącym interfejsem, możesz nie mieć luksusu, że możesz go zadeklarować inaczej niż zwykłą starą funkcję. Standard mógłby zostać zaprojektowany w taki sposób, aby umożliwić generowanie nowego wystąpienia zwykłej starej funkcji za każdym razem, gdy takie wyrażenie lambda jest wykonywane, z wygenerowanymi zakodowanymi odniesieniami do przechwyconych zmiennych. Wygląda na to, że funkcja lambda jest generowana w czasie kompilacji. Są też inne konsekwencje. np. jeśli zadeklarujesz zmienną statyczną, nawet jeśli ponownie ocenisz wyrażenie lambda, nie otrzymasz nowej zmiennej statycznej.
Ted
1
wskaźnik funkcji jest często przeznaczony do zapisania, a przechwytywanie lambdas może wyjść poza zakres. że tylko
lambdy bez
1
Nadal musisz zwracać uwagę na to, że zmienne stosu są zwalniane z tego samego powodu. Zobacz blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/ ... Przykład, który napisałem z danymi wyjściowymi i zastosowaniem, został napisany, tak aby zamiast tego dozwolone i używane były wskaźniki funkcji, działałyby również. Kol pozostaje przydzielony do momentu zakończenia wszystkich wywołań funkcji z Apply. Jak przepisałbyś ten kod do pracy przy użyciu istniejącego interfejsu wprowadzania? Czy użyłbyś zmiennych globalnych lub statycznych, czy też bardziej niejasnej transformacji kodu?
Ted
1
a może po prostu masz na myśli, że wyrażenia lambda są wartościami wartościowymi, a zatem tymczasowymi, ale kod pozostaje stały (singleton / static), aby można go było wywołać w przyszłości. W takim przypadku być może funkcja powinna pozostać przydzielona, dopóki jej przechwytywania przydzielone przez stos pozostaną przydzielone. Oczywiście może być bałagan odwijający, jeśli na przykład wiele wariantów funkcji jest przydzielonych w pętli.
Ted
12
Jednym z najlepszych wyjaśnień lambda expressionjest autor C ++ Bjarne Stroustrup w swoim ***The C++ Programming Language***rozdziale książkowym 11 ( ISBN-13: 978-0321563842 ):
What is a lambda expression?
Ekspresja N , czasami określane mianem lambda
funkcji lub (niesłusznie ściśle mówiąc, ale potocznie) w
lambda , to uproszczony notacja dla definiowania i stosując anonimowego obiektu funkcji . Zamiast definiować nazwaną klasę za pomocą operatora (), później tworząc obiekt tej klasy i wreszcie wywołując ją, możemy użyć skrótu.
When would I use one?
Jest to szczególnie przydatne, gdy chcemy przekazać operację jako argument do algorytmu. W kontekście graficznych interfejsów użytkownika (i innych miejsc) takie operacje są często nazywane wywołaniami zwrotnymi .
What class of problem do they solve that wasn't possible prior to their introduction?
Tutaj wydaje mi się, że każdą akcję wykonaną za pomocą wyrażenia lambda można rozwiązać bez nich, ale z dużo większym kodem i znacznie większą złożonością. Wyrażenie lambda jest to sposób optymalizacji kodu i sposób uczynienia go bardziej atrakcyjnym. Jak smutne przez Stroustup:
skuteczne sposoby optymalizacji
Some examples
poprzez wyrażenie lambda
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';});}
lub poprzez funkcję
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';}};
lub nawet
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});}
jeśli potrzebujesz, możesz wymienić lambda expressionjak poniżej:
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);}
Lub załóż inną prostą próbkę
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);});}
wygeneruje następny
0
1
0
1
0
1
0
1
0
1
0 sortedx - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;
[]- jest to lista przechwytywania lub lambda introducer: jeśli lambdasnie wymagają dostępu do lokalnego środowiska, możemy z niego skorzystać.
Cytat z książki:
Pierwszym znakiem wyrażenia lambda jest zawsze [ . Wprowadzający lambda może przybierać różne formy:
• [] : pusta lista przechwytywania. Oznacza to, że w ciele lambda nie można używać żadnych lokalnych nazw z otaczającego kontekstu. W przypadku takich wyrażeń lambda dane są uzyskiwane z argumentów lub zmiennych nielokalnych.
• [&] : domyślnie przechwytywanie przez odniesienie. Można używać wszystkich lokalnych nazw. Dostęp do wszystkich zmiennych lokalnych można uzyskać przez odniesienie.
• [=] : domyślnie przechwytywanie według wartości. Można używać wszystkich lokalnych nazw. Wszystkie nazwy odnoszą się do kopii zmiennych lokalnych pobranych w punkcie wywołania wyrażenia lambda.
• [lista przechwytywania]: jawne przechwytywanie; lista przechwytywania to lista nazw zmiennych lokalnych, które mają zostać przechwycone (tj. zapisane w obiekcie) przez odniesienie lub wartość. Zmienne o nazwach poprzedzonych znakiem & są przechwytywane przez odniesienie. Inne zmienne są przechwytywane według wartości. Lista przechwytywania może również zawierać to i nazwy, po których następuje ... jako elementy.
• [&, capture-list] : domyślnie przechwytuj przez odniesienie wszystkie zmienne lokalne o nazwach niewymienionych na liście. Lista przechwytywania może to zawierać. Nazw wymienionych na liście nie można poprzedzać &. Zmienne nazwane na liście przechwytywania są przechwytywane według wartości.
• [= lista przechwytywania] : domyślnie przechwytuj według wartości wszystkie zmienne lokalne o nazwach niewymienionych na liście. Lista przechwytywania nie może tego zawierać. Wymienione nazwy muszą być poprzedzone &. Zmienne wymienione na liście przechwytywania są przechwytywane przez odniesienie.
Zauważ, że nazwa lokalna poprzedzona znakiem & jest zawsze przechwytywana przez odniesienie, a nazwa lokalna nieprzedstawiona przez & jest zawsze przechwytywana przez wartość. Tylko przechwytywanie przez odniesienie pozwala modyfikować zmienne w środowisku wywołującym.
Ładne wyjaśnienie. Używając pętli opartych na zakresie, możesz uniknąć lambd i skrócić kodfor (int x : v) { if (x % m == 0) os << x << '\n';}
Dietrich Baumgarten
2
Odkryłem, że jednym praktycznym zastosowaniem jest ograniczenie kodu płyty kotła. Na przykład:
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);//... }
Bez lambda może być konieczne zrobienie czegoś w różnych bsizeprzypadkach. Oczywiście możesz stworzyć funkcję, ale co jeśli chcesz ograniczyć użycie w ramach funkcji użytkownika duszy? natura lambda spełnia ten wymóg i używam go w tym przypadku.
Lambda w c ++ są traktowane jako „dostępna funkcja on the go”. tak, dosłownie w podróży, definiujesz to; Użyj tego; a po zakończeniu zakresu funkcji rodzica funkcja lambda zniknęła.
Opiszę, czego nie ma, ale trzeba wiedzieć dla każdego programisty c ++
Lambda nie jest przeznaczona do stosowania wszędzie i każdej funkcji nie można zastąpić lambda. Nie jest to również najszybszy w porównaniu do normalnej funkcji. ponieważ ma pewne koszty ogólne, którymi musi zająć się lambda.
z pewnością pomoże w niektórych przypadkach zmniejszyć liczbę linii. można go zasadniczo użyć do sekcji kodu, która jest wywoływana w tej samej funkcji raz lub więcej razy i ten fragment kodu nie jest potrzebny nigdzie indziej, aby można było utworzyć dla niego samodzielną funkcję.
Poniżej znajduje się podstawowy przykład lambda i tego, co dzieje się w tle.
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;}
tak jak widać, jaki rodzaj narzutu dodaje, gdy go używasz. więc nie warto używać ich wszędzie. można go stosować w miejscach, w których mają one zastosowanie.
tak, dosłownie w podróży, definiujesz to; Użyj tego; a po zakończeniu zakresu funkcji rodzica funkcja lambda zniknie . Co jeśli funkcja zwróci lambda do programu wywołującego?
Nawaz
1
Nie jest to również najszybszy w porównaniu do normalnej funkcji. ponieważ ma pewne koszty ogólne, którymi musi zająć się lambda. Czy kiedykolwiek rzeczywiście uruchomić żadnego odniesienia do poparcia tego twierdzenia ? Przeciwnie, szablony lambda + często generują najszybszy możliwy kod.
Odpowiedzi:
Problem
C ++ zawiera przydatne funkcje ogólne, takie jak
std::for_each
istd::transform
, które mogą być bardzo przydatne. Niestety mogą być również uciążliwe w użyciu, szczególnie jeśli funktor , który chcesz zastosować, jest unikalny dla konkretnej funkcji.Jeśli użyjesz tylko
f
raz i w tym konkretnym miejscu, wydaje się przesadą pisać całą klasę, aby zrobić coś trywialnego i jednorazowego.W C ++ 03 możesz mieć ochotę napisać coś takiego, aby utrzymać funktor lokalny:
jednak nie jest to dozwolone,
f
nie można go przekazać do funkcji szablonu w C ++ 03.Nowe rozwiązanie
C ++ 11 wprowadza lambdas, które pozwalają napisać wbudowany, anonimowy funktor, który zastąpi
struct f
. W przypadku małych prostych przykładów może to być łatwiejsze do odczytania (utrzymuje wszystko w jednym miejscu) i potencjalnie prostsze w utrzymaniu, na przykład w najprostszej formie:Funkcje lambda to po prostu cukier syntaktyczny dla anonimowych funktorów.
Typy zwrotów
W prostych przypadkach obliczany jest typ zwrotu lambda, np .:
jednak kiedy zaczniesz pisać bardziej złożone lambdy, szybko napotkasz przypadki, w których kompilator nie może wydedukować typu zwrotu, np .:
Aby rozwiązać ten problem, możesz jawnie określić typ zwracany dla funkcji lambda, używając
-> T
:Zmienne „przechwytywanie”
Do tej pory nie używaliśmy niczego innego niż to, co zostało przekazane do niej w lambdzie, ale możemy również używać innych zmiennych w obrębie lambda. Jeśli chcesz uzyskać dostęp do innych zmiennych, możesz użyć klauzuli przechwytywania (
[]
wyrażenia), która do tej pory nie była używana w tych przykładach, np .:Możesz przechwytywać zarówno według odwołania, jak i wartości, które możesz określić za pomocą
&
i=
odpowiednio:[&epsilon]
przechwytywanie przez odniesienie[&]
przechwytuje wszystkie zmienne używane w lambda przez odniesienie[=]
przechwytuje wszystkie zmienne używane w lambda według wartości[&, epsilon]
przechwytuje zmienne takie jak z [&], ale epsilon według wartości[=, &epsilon]
przechwytuje zmienne jak z [=], ale epsilon przez odniesienieGenerowane
operator()
jestconst
domyślnie, z implikacją, że przechwytywanie nastąpiconst
po domyślnym dostępie do nich. Powoduje to, że każde wywołanie z tym samym wejściem daje ten sam wynik, jednak można oznaczyć lambda jakomutable
żądanie,operator()
aby nie zostało wygenerowaneconst
.źródło
const
zawsze ...()
- jest przekazywany jako lambda z zerowym argumentem, ale ponieważ() const
nie pasuje do lambda, szuka konwersji typu, która na to pozwala, w tym niejawnego rzutowania -do-funkcji-wskaźnik, a następnie wywołuje to! Podstępny!std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };
Ale zwykle pozwalamy kompilatorowi wydedukować typ:auto f = [](int a, bool b) -> double { ... };
(i nie zapomnij#include <functional>
)return d < 0.00001 ? 0 : d;
gwarantowany jest zwrot podwójny, gdy jeden z operandów jest stałą całkowitą (wynika to z domyślnej reguły promocji operatora?: Operator, w której drugi i trzeci operand są równoważone względem siebie za pomocą zwykłej arytmetyki konwersje bez względu na to, który zostanie wybrany). Zmiana na0.0 : d
być może ułatwiłaby zrozumienie przykładu.Co to jest funkcja lambda?
Koncepcja C ++ funkcji lambda wywodzi się z rachunku lambda i programowania funkcjonalnego. Lambda jest nienazwaną funkcją przydatną (w rzeczywistym programowaniu, nie w teorii) dla krótkich fragmentów kodu, których nie można ponownie użyć i których nie warto nazywać.
W C ++ funkcja lambda jest zdefiniowana w ten sposób
lub w całej okazałości
[]
to lista przechwytywania, lista()
argumentów i{}
treść funkcji.Lista przechwytywania
Lista przechwytywania określa, co z zewnątrz lambda powinno być dostępne wewnątrz ciała funkcji i jak. Może to być:
Możesz mieszać dowolne z powyższych elementów na liście oddzielonej przecinkami
[x, &y]
.Lista argumentów
Lista argumentów jest taka sama jak w każdej innej funkcji C ++.
Ciało funkcji
Kod, który zostanie wykonany po wywołaniu lambda.
Odliczenie typu zwrotu
Jeśli lambda ma tylko jedną instrukcję return, typ zwracany można pominąć i ma domyślny typ
decltype(return_statement)
.Zmienny
Jeśli lambda jest oznaczona jako zmienna (np.
[]() mutable { }
), Można mutować wartości, które zostały przechwycone przez wartość.Przypadków użycia
Biblioteka zdefiniowana w standardzie ISO znacznie korzysta z lambdas i podnosi użyteczność kilku pasków, ponieważ teraz użytkownicy nie muszą zaśmiecać kodu małymi funktorami w dostępnym zakresie.
C ++ 14
W C ++ 14 lambdów zostało poszerzonych o różne propozycje.
Zainicjowano przechwyty lambda
Teraz można zainicjować element listy przechwytywania
=
. Pozwala to na zmianę nazw zmiennych i przechwytywanie przez przenoszenie. Przykład wzięty ze standardu:i jeden wzięty z Wikipedii pokazujący, jak robić zdjęcia
std::move
:Ogólne Lambdas
Lambdas mogą być teraz ogólne (
auto
byłoby to równoważne zT
tym, gdybyT
gdzieś w otaczającym zakresie był argument szablonu typu):Ulepszone odliczenie typu zwrotu
C ++ 14 pozwala przewidywać typy zwrotów dla każdej funkcji i nie ogranicza jej do funkcji formularza
return expression;
. Dotyczy to również lambd.źródło
r = &x; r += 2;
ale dzieje się tak z pierwotną wartością 4.Wyrażenia lambda są zwykle używane do enkapsulacji algorytmów, aby można je było przekazać do innej funkcji. Jednak możliwe jest natychmiastowe wykonanie lambda od definicji :
jest funkcjonalnie równoważny z
To sprawia, że wyrażenia lambda są potężnym narzędziem do refaktoryzacji złożonych funkcji . Zaczynasz od zawijania sekcji kodu w funkcji lambda, jak pokazano powyżej. Proces jawnej parametryzacji można następnie przeprowadzać stopniowo za pomocą testów pośrednich po każdym kroku. Po pełnym sparametryzowaniu bloku kodu (jak pokazano po usunięciu
&
), możesz przenieść kod do zewnętrznego miejsca i ustawić go jako normalną funkcję.Podobnie możesz użyć wyrażeń lambda do zainicjowania zmiennych na podstawie wyniku algorytmu ...
Jako sposób na podzielenie logiki programu może okazać się przydatne przekazanie wyrażenia lambda jako argumentu do innego wyrażenia lambda ...
Wyrażenia lambda pozwalają również tworzyć nazwane funkcje zagnieżdżone , co może być wygodnym sposobem uniknięcia podwójnej logiki. Używanie nazwanych lambdas jest również nieco łatwiejsze dla oczu (w porównaniu do anonimowych lambdów wbudowanych), gdy przekazuje się nietrywialną funkcję jako parametr do innej funkcji. Uwaga: nie zapomnij o średniku po zamykającym nawiasie klamrowym.
Jeśli kolejne profilowanie ujawni znaczny narzut związany z inicjalizacją dla obiektu funkcji, możesz przepisać to jako normalną funkcję.
źródło
if
oświadczeń:if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace
zakładając, żei
jeststd::string
[](){}();
.(lambda: None)()
Składnia Pythona jest o wiele bardziej czytelna.main() {{{{((([](){{}}())));}}}}
Odpowiedzi
P: Co to jest wyrażenie lambda w C ++ 11?
Odp .: Pod maską jest to obiekt klasy generowanej automatycznie z przeciążeniem operatora () const . Taki obiekt nazywa się zamknięciem i jest tworzony przez kompilator. Ta koncepcja „zamknięcia” jest zbliżona do koncepcji wiązania z C ++ 11. Ale lambda zazwyczaj generują lepszy kod. Połączenia przez zamknięcia umożliwiają pełne wstawianie.
P: Kiedy miałbym go użyć?
Odp .: Aby zdefiniować „prostą i małą logikę” i poprosić kompilatora o wykonanie generacji z poprzedniego pytania. Dajesz kompilatorowi kilka wyrażeń, które mają znajdować się wewnątrz operatora (). Wszystkie inne kompilatory rzeczy wygenerują dla Ciebie.
P: Jakiego rodzaju problemy rozwiązują, które nie były możliwe przed ich wprowadzeniem?
Odp .: Jest to rodzaj cukru składniowego, takiego jak przeciążanie operatorów zamiast funkcji do niestandardowych operacji dodawania, subrtact ... Zapisuje jednak więcej wierszy niepotrzebnego kodu, aby zawrzeć 1-3 linie prawdziwej logiki w niektórych klasach itp.! Niektórzy inżynierowie uważają, że jeśli liczba wierszy jest mniejsza, wówczas istnieje mniejsze prawdopodobieństwo popełnienia błędów (tak też uważam)
Przykład użycia
Dodatki dotyczące lambdas, nie objęte pytaniem. Zignoruj tę sekcję, jeśli nie jesteś zainteresowany
1. Przechwycone wartości. Co możesz uchwycić
1.1 Możesz odwoływać się do zmiennej o statycznym czasie przechowywania w lambdas. Wszyscy zostali schwytani.
1.2 Możesz użyć lambda do przechwytywania wartości „według wartości”. W takim przypadku przechwycone zmienne zostaną skopiowane do obiektu funkcji (zamknięcie).
1.3 Możesz przechwycić jako odniesienie. & - w tym kontekście oznacza odniesienie, a nie wskaźniki.
1.4 Istnieje notacja do przechwytywania wszystkich zmiennych niestatycznych według wartości lub referencji
1.5 Istnieje notacja do przechwytywania wszystkich zmiennych niestatycznych według wartości lub referencji i określania smth. więcej. Przykłady: Przechwyć wszystkie zmienne statyczne według wartości, ale poprzez odniesienie przechwyć Param2
Przechwyć wszystkie zmienne statyczne przez odniesienie, ale przez przechwycenie wartości Param2
2. Odliczenie typu zwrotu
2.1 Typ powrotu lambda można wywnioskować, jeśli lambda jest jednym wyrażeniem. Lub możesz to wyraźnie określić.
Jeśli lambda ma więcej niż jedno wyrażenie, to typ zwracany musi być określony za pomocą końcowego typu zwracanego. Podobną składnię można również zastosować do funkcji automatycznych i funkcji składowych
3. Przechwycone wartości. Czego nie można uchwycić
3.1 Można przechwytywać tylko zmienne lokalne, a nie zmienną składową obiektu.
4. Konwersje
4.1 !! Lambda nie jest wskaźnikiem funkcji i nie jest funkcją anonimową, ale lambdy bez przechwytywania można pośrednio przekonwertować na wskaźnik funkcji.
ps
Więcej informacji na temat gramatyki lambda można znaleźć w Roboczym szkicu dla języka programowania C ++ # 337, 16.01.2012, 5.1.2. Wyrażenia lambda, s. 88
W C ++ 14 dodano dodatkową funkcję o nazwie „przechwytywanie init”. Pozwala na arbitrażowe zgłaszanie członków danych zamknięcia:
źródło
[&,=Param2](int arg1){}
nie wydaje się poprawna składnia. Prawidłowa forma to[&,Param2](int arg1){}
Funkcja lambda to anonimowa funkcja tworzona bezpośrednio. Może przechwytywać zmienne, jak niektórzy wyjaśnili (np. Http://www.stroustrup.com/C++11FAQ.html#lambda ), ale istnieją pewne ograniczenia. Na przykład jeśli istnieje taki interfejs zwrotny,
możesz napisać funkcję na miejscu, aby użyć jej tak jak tej, którą zastosowano poniżej:
Ale nie możesz tego zrobić:
z powodu ograniczeń w standardzie C ++ 11. Jeśli chcesz używać przechwytywania, musisz polegać na bibliotece i
(lub inny algorytm podobny do biblioteki STL, aby uzyskać go pośrednio), a następnie pracować ze std :: function zamiast przekazywać normalne funkcje jako parametry takie jak to:
źródło
apply
był szablonem, który akceptuje funktor, działałbyJednym z najlepszych wyjaśnień
lambda expression
jest autor C ++ Bjarne Stroustrup w swoim***The C++ Programming Language***
rozdziale książkowym 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?
Tutaj wydaje mi się, że każdą akcję wykonaną za pomocą wyrażenia lambda można rozwiązać bez nich, ale z dużo większym kodem i znacznie większą złożonością. Wyrażenie lambda jest to sposób optymalizacji kodu i sposób uczynienia go bardziej atrakcyjnym. Jak smutne przez Stroustup:
Some examples
poprzez wyrażenie lambda
lub poprzez funkcję
lub nawet
jeśli potrzebujesz, możesz wymienić
lambda expression
jak poniżej:Lub załóż inną prostą próbkę
wygeneruje następny
[]
- jest to lista przechwytywania lublambda introducer
: jeślilambdas
nie wymagają dostępu do lokalnego środowiska, możemy z niego skorzystać.Cytat z książki:
Additional
Lambda expression
formatDodatkowe referencje:
źródło
for (int x : v) { if (x % m == 0) os << x << '\n';}
Odkryłem, że jednym praktycznym zastosowaniem jest ograniczenie kodu płyty kotła. Na przykład:
Bez lambda może być konieczne zrobienie czegoś w różnych
bsize
przypadkach. Oczywiście możesz stworzyć funkcję, ale co jeśli chcesz ograniczyć użycie w ramach funkcji użytkownika duszy? natura lambda spełnia ten wymóg i używam go w tym przypadku.źródło
Lambda w c ++ są traktowane jako „dostępna funkcja on the go”. tak, dosłownie w podróży, definiujesz to; Użyj tego; a po zakończeniu zakresu funkcji rodzica funkcja lambda zniknęła.
c ++ wprowadził go w c ++ 11 i wszyscy zaczęli go używać jak w każdym możliwym miejscu. przykład i czym jest lambda można znaleźć tutaj https://en.cppreference.com/w/cpp/language/lambda
Opiszę, czego nie ma, ale trzeba wiedzieć dla każdego programisty c ++
Lambda nie jest przeznaczona do stosowania wszędzie i każdej funkcji nie można zastąpić lambda. Nie jest to również najszybszy w porównaniu do normalnej funkcji. ponieważ ma pewne koszty ogólne, którymi musi zająć się lambda.
z pewnością pomoże w niektórych przypadkach zmniejszyć liczbę linii. można go zasadniczo użyć do sekcji kodu, która jest wywoływana w tej samej funkcji raz lub więcej razy i ten fragment kodu nie jest potrzebny nigdzie indziej, aby można było utworzyć dla niego samodzielną funkcję.
Poniżej znajduje się podstawowy przykład lambda i tego, co dzieje się w tle.
Kod użytkownika:
Jak kompiluje to:
tak jak widać, jaki rodzaj narzutu dodaje, gdy go używasz. więc nie warto używać ich wszędzie. można go stosować w miejscach, w których mają one zastosowanie.
źródło
Jeden problem, który rozwiązuje: kod prostszy niż lambda dla wywołania w konstruktorze, który używa funkcji parametru wyjściowego do inicjowania elementu const
Możesz zainicjować stałego członka swojej klasy, wywołując funkcję, która ustawia jej wartość, zwracając jej wynik jako parametr wyjściowy.
źródło