Ich habe einen Fall gesehen, in dem das Lambda sehr nützlich war: Ein Kollege von mir hat Code mit Millionen von Iterationen erstellt, um ein Problem mit der Raumoptimierung zu lösen. Der Algorithmus war bei Verwendung eines Lambdas viel schneller als eine ordnungsgemäße Funktion! Der Compiler ist Visual C ++ 2013.
Sergiol
Antworten:
1490
Das Problem
C ++ enthält nützliche generische Funktionen wie std::for_eachund std::transform, die sehr praktisch sein können. Leider kann die Verwendung auch sehr umständlich sein, insbesondere wenn der Funktor, den Sie anwenden möchten, für die jeweilige Funktion eindeutig ist.
#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);}
Wenn Sie nur feinmal und an diesem bestimmten Ort verwenden, scheint es übertrieben, eine ganze Klasse zu schreiben, nur um etwas Triviales und Einmaliges zu tun.
In C ++ 03 könnten Sie versucht sein, Folgendes zu schreiben, um den Funktor lokal zu halten:
void func2(std::vector<int>& v){struct{voidoperator()(int){// do something}} f;
std::for_each(v.begin(), v.end(), f);}
In C ++ 11 werden Lambdas eingeführt, mit denen Sie einen anonymen Inline-Funktor schreiben können, der den ersetzt struct f. Für kleine einfache Beispiele kann dies sauberer zu lesen sein (es hält alles an einem Ort) und möglicherweise einfacher zu warten, zum Beispiel in der einfachsten Form:
void func3(std::vector<int>& v){
std::for_each(v.begin(), v.end(),[](int){/* do something here*/});}
Lambda-Funktionen sind nur syntaktischer Zucker für anonyme Funktoren.
Rückgabetypen
In einfachen Fällen wird für Sie der Rückgabetyp des Lambda abgeleitet, z.
void func4(std::vector<double>& v){
std::transform(v.begin(), v.end(), v.begin(),[](double d){return d <0.00001?0: d;});}
Wenn Sie jedoch anfangen, komplexere Lambdas zu schreiben, werden Sie schnell auf Fälle stoßen, in denen der Rückgabetyp vom Compiler nicht abgeleitet werden kann, z.
Bisher haben wir nichts anderes verwendet als das, was an das Lambda darin übergeben wurde, aber wir können auch andere Variablen innerhalb des Lambda verwenden. Wenn Sie auf andere Variablen zugreifen möchten, können Sie die Capture-Klausel (die []des Ausdrucks) verwenden, die in diesen Beispielen bisher nicht verwendet wurde, z.
Sie können sowohl von Referenz- und Wert erfassen, die Sie verwenden können angeben &und =jeweils:
[&epsilon] Erfassung durch Referenz
[&] erfasst alle im Lambda verwendeten Variablen als Referenz
[=] Erfasst alle im Lambda verwendeten Variablen nach Wert
[&, epsilon] Erfasst Variablen wie mit [&], aber epsilon nach Wert
[=, &epsilon] erfasst Variablen wie mit [=], jedoch epsilon als Referenz
Die Generierung operator()erfolgt conststandardmäßig mit der Folge, dass die Erfassung standardmäßig erfolgt, constwenn Sie darauf zugreifen. Dies hat zur Folge, dass jeder Aufruf mit derselben Eingabe dasselbe Ergebnis liefert. Sie können jedoch das Lambda markieren, ummutable zu fordern, dass das operator()erzeugte nicht vorhanden ist const.
@ Yakk du wurdest gefangen. Lambdas ohne Capture haben eine implizite Konvertierung in Funktionstypzeiger. Die Konvertierungsfunktion ist constimmer ...
Johannes Schaub - Litb
2
@ JohannesSchaub-litb oh hinterhältig - und es passiert, wenn Sie aufrufen ()- es wird als Lambda mit null Argumenten übergeben, aber da () constes nicht mit dem Lambda übereinstimmt, sucht es nach einer Typkonvertierung, die dies ermöglicht, einschließlich impliziter Umwandlung -to-function-pointer und ruft das dann auf! Hinterhältig!
Yakk - Adam Nevraumont
2
Interessant - Ich dachte ursprünglich, dass Lambdas eher anonyme Funktionen als Funktoren sind, und war verwirrt darüber, wie Captures funktionieren.
user253751
49
Wenn Sie Lambdas als Variablen in Ihrem Programm verwenden möchten, können Sie Folgendes verwenden: std::function<double(int, bool)> f = [](int a, bool b) -> double { ... }; Normalerweise lassen wir den Compiler den Typ ableiten: auto f = [](int a, bool b) -> double { ... }; (und vergessen Sie nicht #include <functional>)
Evert Heylen
11
Ich nehme an, nicht jeder versteht, warum return d < 0.00001 ? 0 : d;garantiert doppelt zurückgegeben wird, wenn einer der Operanden eine ganzzahlige Konstante ist (dies liegt an einer impliziten Heraufstufungsregel des Operators ?:, Bei der der 2. und 3. Operand durch die übliche Arithmetik gegeneinander abgewogen werden Konvertierungen, egal welche ausgewählt wird). Ein Wechsel zu 0.0 : dwürde das Beispiel vielleicht leichter verständlich machen.
Lundin
830
Was ist eine Lambda-Funktion?
Das C ++ - Konzept einer Lambda-Funktion stammt aus dem Lambda-Kalkül und der funktionalen Programmierung. Ein Lambda ist eine unbenannte Funktion, die (in der eigentlichen Programmierung, nicht in der Theorie) für kurze Codeausschnitte nützlich ist, die nicht wiederverwendet werden können und deren Benennung nicht wert ist.
In C ++ ist eine Lambda-Funktion wie folgt definiert
[](){}// barebone lambda
oder in all seiner Pracht
[]()mutable-> T {}// T is the return type, still lacking throw()
[]ist die Erfassungsliste, ()die Argumentliste und {}der Funktionskörper.
Die Erfassungsliste
Die Erfassungsliste definiert, was von außerhalb des Lambda innerhalb des Funktionskörpers verfügbar sein soll und wie. Es kann entweder sein:
ein Wert: [x]
eine Referenz [& x]
jede Variable, die derzeit durch Bezugnahme im Geltungsbereich liegt [&]
wie 3, jedoch nach Wert [=]
Sie können eine der oben genannten Optionen in einer durch Kommas getrennten Liste mischen [x, &y].
Die Argumentliste
Die Argumentliste ist dieselbe wie in jeder anderen C ++ - Funktion.
Der Funktionskörper
Der Code, der ausgeführt wird, wenn das Lambda tatsächlich aufgerufen wird.
Abzug vom Rückgabetyp
Wenn ein Lambda nur eine return-Anweisung hat, kann der return-Typ weggelassen werden und hat den impliziten Typ von decltype(return_statement).
Veränderlich
Wenn ein Lambda als veränderlich markiert ist (z. B. []() mutable { }), darf es die Werte mutieren, die durch den Wert erfasst wurden.
Anwendungsfälle
Die durch den ISO-Standard definierte Bibliothek profitiert stark von Lambdas und erhöht die Benutzerfreundlichkeit um mehrere Balken, da Benutzer ihren Code jetzt nicht mehr mit kleinen Funktoren in einem zugänglichen Bereich überladen müssen.
C ++ 14
In C ++ 14 wurden Lambdas um verschiedene Vorschläge erweitert.
Initialisierte Lambda-Aufnahmen
Ein Element der Erfassungsliste kann jetzt mit initialisiert werden =. Dies ermöglicht das Umbenennen von Variablen und das Erfassen durch Verschieben. Ein Beispiel aus dem Standard:
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.
und eine aus Wikipedia, die zeigt, wie man mit std::move:
auto ptr = std::make_unique<int>(10);// See below for std::make_uniqueauto lambda =[ptr = std::move(ptr)]{return*ptr;};
Generische Lambdas
Lambdas können jetzt generisch sein ( autowäre hier äquivalent, Twenn
Tsich irgendwo im umgebenden Bereich ein Typvorlagenargument befindet ):
auto lambda =[](auto x,auto y){return x + y;};
Verbesserter Abzug vom Rückgabetyp
C ++ 14 erlaubt abgeleitete Rückgabetypen für jede Funktion und beschränkt sie nicht auf Funktionen des Formulars return expression;. Dies gilt auch für Lambdas.
Warum beenden Sie in Ihrem obigen Beispiel für initialisierte Lambda-Captures die Lamba-Funktion mit ();? Dies erscheint wie [] () {} (); Anstatt von [](){};. Sollte der Wert von x auch nicht 5 sein?
Ramakrishnan Kannan
6
@RamakrishnanKannan: 1) Die () sind dazu da, das Lambda direkt nach der Definition aufzurufen und y seinen Rückgabewert zu geben. Die Variable y ist eine ganze Zahl, nicht das Lambda. 2) Nein, x = 5 ist lokal für das Lambda (eine Erfassung nach Wert, die zufällig denselben Namen wie die äußere Bereichsvariable x hat), und dann wird x + 2 = 5 + 2 zurückgegeben. Die Neuzuweisung der äußeren Variablen x erfolgt über die Referenz r : r = &x; r += 2;, dies geschieht jedoch mit dem ursprünglichen Wert von 4.
The Vee
167
Lambda-Ausdrücke werden normalerweise verwendet, um Algorithmen zu kapseln, damit sie an eine andere Funktion übergeben werden können. Allerdings ist es möglich , eine Lambda unmittelbar nach Definition auszuführen :
Dies macht Lambda-Ausdrücke zu einem leistungsstarken Werkzeug für die Umgestaltung komplexer Funktionen . Sie beginnen, indem Sie einen Codeabschnitt wie oben gezeigt in eine Lambda-Funktion einschließen. Der Prozess der expliziten Parametrisierung kann dann nach jedem Schritt schrittweise mit Zwischentests durchgeführt werden. Sobald Sie den Codeblock vollständig parametrisiert haben (wie durch das Entfernen des Codeblocks gezeigt &), können Sie den Code an einen externen Speicherort verschieben und zu einer normalen Funktion machen.
Ebenso können Sie Lambda-Ausdrücke verwenden, um Variablen basierend auf dem Ergebnis eines Algorithmus zu initialisieren ...
int a =[](int b ){int r=1;while(b>0) r*=b--;return r;}(5);// 5!
Als eine Möglichkeit , Ihre Programmlogik der Partitionierung , könnten Sie sogar finden es sinnvoll , einen Lambda - Ausdruck als Argument für einen anderen Lambda - Ausdruck passieren ...
Mit Lambda-Ausdrücken können Sie auch benannte verschachtelte Funktionen erstellen. Dies kann eine bequeme Möglichkeit sein, doppelte Logik zu vermeiden. Die Verwendung von benannten Lambdas ist auch für die Augen etwas einfacher (im Vergleich zu anonymen Inline-Lambdas), wenn eine nicht triviale Funktion als Parameter an eine andere Funktion übergeben wird. Hinweis: Vergessen Sie das Semikolon nach der schließenden geschweiften Klammer nicht.
auto algorithm =[&](double x,double m,double b )->double{return m*x+b;};int a=algorithm(1,2,3), b=algorithm(4,5,6);
Wenn die nachfolgende Profilerstellung einen erheblichen Initialisierungsaufwand für das Funktionsobjekt ergibt, können Sie dies als normale Funktion umschreiben.
Haben Sie bemerkt, dass diese Frage vor 1,5 Jahren gestellt wurde und dass die letzte Aktivität vor fast 1 Jahr war? Wie auch immer, Sie bringen einige interessante Ideen ein, die ich noch nie gesehen habe!
Piotr99
7
Vielen Dank für den Tipp zum gleichzeitigen Definieren und Ausführen! Ich denke, es ist erwähnenswert, dass dies als Voraussetzung für ifAussagen funktioniert : if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespaceVorausgesetzt, es ihandelt sich um einstd::string
Blacklight Shining
74
Das Folgende ist also ein rechtlicher Ausdruck : [](){}();.
Nobar
8
Pfui! Die (lambda: None)()Syntax von Python ist viel besser lesbar.
Dan04
9
@nobar - du hast recht, ich habe falsch geschrieben. Dies ist legal (ich habe es dieses Mal getestet)main() {{{{((([](){{}}())));}}}}
Mark Lakata
38
Antworten
F: Was ist ein Lambda-Ausdruck in C ++ 11?
A: Unter der Haube ist es das Objekt einer automatisch generierten Klasse mit Überladungsoperator () const . Ein solches Objekt wird als Closure bezeichnet und vom Compiler erstellt. Dieses 'Closure'-Konzept kommt dem Bind-Konzept aus C ++ 11 nahe. Aber Lambdas generieren normalerweise besseren Code. Und Anrufe durch Verschlüsse ermöglichen ein vollständiges Inlining.
F: Wann würde ich einen verwenden?
A: Um "einfache und kleine Logik" zu definieren und den Compiler zu bitten, führen Sie die Generierung aus der vorherigen Frage durch. Sie geben einem Compiler einige Ausdrücke, die Sie in operator () haben möchten. Alle anderen Sachen, die der Compiler für Sie generiert.
F: Welche Klasse von Problemen lösen sie, die vor ihrer Einführung nicht möglich waren?
A: Es ist eine Art Syntaxzucker wie das Überladen von Operatoren anstelle von Funktionen für benutzerdefinierte Add-, Subrtact- Operationen ... Es werden jedoch mehr Zeilen nicht benötigten Codes gespeichert , um 1-3 Zeilen echte Logik in einige Klassen usw. zu verpacken! Einige Ingenieure sind der Meinung, dass bei einer geringeren Anzahl von Zeilen die Wahrscheinlichkeit geringer ist, dass Fehler auftreten (ich denke auch).
Anwendungsbeispiel
auto x =[=](int arg1){printf("%i", arg1);};void(*f)(int)= x;
f(1);
x(1);
Extras über Lambdas, nicht in Frage gestellt. Ignorieren Sie diesen Abschnitt, wenn Sie nicht interessiert sind
1. Erfasste Werte. Was können Sie erfassen
1.1. Sie können auf eine Variable mit statischer Speicherdauer in Lambdas verweisen. Sie alle werden gefangen genommen.
1.2. Sie können Lambda verwenden, um Werte "nach Wert" zu erfassen. In diesem Fall werden erfasste Variablen in das Funktionsobjekt kopiert (Abschluss).
[captureVar1,captureVar2](int arg1){}
1.3. Sie können als Referenz erfassen. & - bedeutet in diesem Zusammenhang Referenz, keine Zeiger.
[&captureVar1,&captureVar2](int arg1){}
1.4. Es gibt eine Notation, um alle nicht statischen Variablen nach Wert oder Referenz zu erfassen
[=](int arg1){}// capture all not-static vars by value[&](int arg1){}// capture all not-static vars by reference
1.5. Es gibt eine Notation, um alle nicht statischen Variablen nach Wert oder Referenz zu erfassen und smth anzugeben. Mehr. Beispiele: Erfassen Sie alle nicht statischen Variablen nach Wert, aber nach Referenzerfassung Param2
[=,&Param2](int arg1){}
Erfassen Sie alle nicht statischen Variablen anhand der Referenz, aber anhand der Werterfassung Param2
[&,Param2](int arg1){}
2. Geben Sie den Typabzug zurück
2.1. Der Lambda-Rückgabetyp kann abgeleitet werden, wenn Lambda ein Ausdruck ist. Oder Sie können es explizit angeben.
Wenn Lambda mehr als einen Ausdruck hat, muss der Rückgabetyp über den nachfolgenden Rückgabetyp angegeben werden. Eine ähnliche Syntax kann auch auf automatische Funktionen und Elementfunktionen angewendet werden
3. Erfasste Werte. Was Sie nicht erfassen können
3.1. Sie können nur lokale Variablen erfassen, keine Mitgliedsvariable des Objekts.
4. versUmwandlungen
4.1 !! Lambda ist kein Funktionszeiger und keine anonyme Funktion, aber lambdas ohne Erfassung können implizit in einen Funktionszeiger konvertiert werden.
ps
Weitere Informationen zur Lambda-Grammatik finden Sie im Arbeitsentwurf für die Programmiersprache C ++ # 337, 16.01.2012, 5.1.2. Lambda Expressions, S.88
In C ++ 14 wurde die zusätzliche Funktion "Init Capture" hinzugefügt. Es ermöglicht die willkürliche Deklaration von Mitgliedern der Schließungsdaten:
auto toFloat =[](int value){returnfloat(value);};auto interpolate =[min = toFloat(0), max = toFloat(255)](int value)->float{return(value - min)/(max - min);};
Dies [&,=Param2](int arg1){}scheint keine gültige Syntax zu sein. Die richtige Form wäre[&,Param2](int arg1){}
GetFree
Vielen Dank. Zuerst habe ich versucht, dieses Snippet zu kompilieren. Und es scheint seltsam, Assymetrie in zulässigen Modifikatoren in der Erfassungsliste // 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) veränderlich {param = arg1;}; f (111); printf ("% i \ n", param); } #endif #if 0 {int param = 0; auto f = [&, = param] (int arg1) veränderlich {param = arg1;}; f (111); printf ("% i \ n", param); } #endif return 0; }
Bruziuz
Sieht so aus, als würde diese neue Zeile im Kommentar nicht unterstützt. Dann öffnete ich 5.1.2 Lambda-Ausdrücke, S.88, "Working Draft, Standard für die Programmiersprache C ++", Dokumentnummer: # 337, 2012-01-16. Und schaute in die Grammatiksyntax. Und du hast recht. Es gibt keine Gefangennahme über "= arg"
bruziuz
Vielen Dank, habe es in der Beschreibung behoben und auch neues Wissen dazu erworben.
Bruziuz
16
Eine Lambda-Funktion ist eine anonyme Funktion, die Sie inline erstellen. Es kann Variablen erfassen, wie einige erklärt haben (z. B. http://www.stroustrup.com/C++11FAQ.html#lambda ), es gibt jedoch einige Einschränkungen. Wenn es beispielsweise eine solche Rückrufschnittstelle gibt,
void apply(void(*f)(int)){
f(10);
f(20);
f(30);}
Sie können eine Funktion an Ort und Stelle schreiben, um sie wie die unten verwendete zu verwenden:
int col=0;void output(){
apply([](int data){
cout << data <<((++col %10)?' ':'\n');});}
aufgrund von Einschränkungen im C ++ 11-Standard. Wenn Sie Captures verwenden möchten, müssen Sie sich auf die Bibliothek und verlassen
#include<functional>
(oder einen anderen STL-Bibliotheks-ähnlichen Algorithmus, um ihn indirekt abzurufen) und dann mit std :: function zu arbeiten, anstatt normale Funktionen als Parameter wie diesen zu übergeben:
Der Grund ist, dass ein Lambda nur dann in einen Funktionszeiger konvertieren kann, wenn es keine Erfassung hat. Wenn applyes eine Vorlage wäre, die einen Funktor akzeptiert, würde sie funktionieren
sp2danny
1
Das Problem ist jedoch, dass Sie, wenn apply eine vorhandene Schnittstelle ist, möglicherweise nicht den Luxus haben, sie anders als eine einfache alte Funktion deklarieren zu können. Der Standard könnte so konzipiert sein, dass bei jeder Ausführung eines solchen Lambda-Ausdrucks eine neue Instanz einer einfachen alten Funktion mit generierten fest codierten Verweisen auf die erfassten Variablen generiert werden kann. Es scheint, dass zur Kompilierungszeit eine Lambda-Funktion generiert wird. Es gibt auch andere Konsequenzen. Beispiel: Wenn Sie eine statische Variable deklarieren, erhalten Sie keine neue statische Variable, auch wenn Sie den Lambda-Ausdruck neu auswerten.
Ted
1
Funktionszeiger sollen häufig gespeichert werden, und eine Lambdas-Erfassung kann außerhalb des Gültigkeitsbereichs liegen. dass nur
lambdas ohne
1
Sie müssen immer noch darauf achten, dass Stapelvariablen aus demselben Grund freigegeben werden. Siehe blogs.msdn.com/b/nativeconcurrency/archive/2012/01/29/… Das Beispiel, das ich mit output and apply geschrieben habe, ist so geschrieben, dass Funktionszeiger auch funktionieren würden, wenn sie stattdessen zugelassen und verwendet würden. Die Spalte bleibt zugewiesen, bis alle Funktionsaufrufe von apply beendet sind. Wie würden Sie diesen Code so umschreiben, dass er mit der vorhandenen Apply-Schnittstelle funktioniert? Würden Sie am Ende globale oder statische Variablen oder eine dunkelere Transformation des Codes verwenden?
Ted
1
oder vielleicht meinen Sie einfach, dass Lambda-Ausdrücke r-Werte und daher temporär sind, der Code jedoch konstant bleibt (Singleton / Static), damit er in Zukunft aufgerufen werden kann. In diesem Fall sollte die Funktion möglicherweise zugewiesen bleiben, solange die vom Stapel zugewiesenen Erfassungen zugewiesen bleiben. Natürlich kann es unordentlich werden, wenn beispielsweise viele Variationen der Funktion in einer Schleife zugewiesen werden.
Ted
12
Eine der besten Erklärungen dafür lambda expressiongibt der Autor von C ++ Bjarne Stroustrup in seinem Buch ***The C++ Programming Language***Kapitel 11 ( ISBN-13: 978-0321563842 ):
What is a lambda expression?
Ein Lambda-Ausdruck , der manchmal auch als Lambda-
Funktion oder (streng genommen falsch, aber umgangssprachlich) als
Lambda bezeichnet wird , ist eine vereinfachte Notation zum Definieren und Verwenden eines anonymen Funktionsobjekts . Anstatt eine benannte Klasse mit einem Operator () zu definieren, später ein Objekt dieser Klasse zu erstellen und es schließlich aufzurufen, können wir eine Kurzform verwenden.
When would I use one?
Dies ist besonders nützlich, wenn wir eine Operation als Argument an einen Algorithmus übergeben möchten. Im Zusammenhang mit grafischen Benutzeroberflächen (und anderswo) werden solche Vorgänge häufig als Rückrufe bezeichnet .
What class of problem do they solve that wasn't possible prior to their introduction?
Hier denke ich, dass jede Aktion, die mit Lambda-Ausdruck ausgeführt wird, ohne sie gelöst werden kann, aber mit viel mehr Code und viel größerer Komplexität. Lambda-Ausdruck Dies ist die Art der Optimierung Ihres Codes und eine Möglichkeit, ihn attraktiver zu machen. So traurig von Stroustup:
effektive Möglichkeiten zur Optimierung
Some examples
über Lambda-Ausdruck
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';});}
oder über Funktion
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';}};
oder auch
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});}
Wenn du brauchst, kannst du lambda expressionwie folgt benennen :
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);}
Oder nehmen Sie eine andere einfache Probe an
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);});}
wird als nächstes generieren
0
1
0
1
0
1
0
1
0
1
0 sortiert x - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;
[]- Dies ist eine Erfassungsliste oder lambda introducer: Wenn lambdaskein Zugriff auf die lokale Umgebung erforderlich ist, können wir sie verwenden.
Zitat aus dem Buch:
Das erste Zeichen eines Lambda-Ausdrucks ist immer [ . Ein Lambda-Einführer kann verschiedene Formen annehmen:
• [] : Eine leere Erfassungsliste. Dies impliziert, dass im Lambda-Körper keine lokalen Namen aus dem umgebenden Kontext verwendet werden können. Für solche Lambda-Ausdrücke werden Daten aus Argumenten oder aus nichtlokalen Variablen erhalten.
• [&] : implizit als Referenz erfassen. Alle lokalen Namen können verwendet werden. Auf alle lokalen Variablen wird als Referenz zugegriffen.
• [=] : implizit nach Wert erfassen. Alle lokalen Namen können verwendet werden. Alle Namen beziehen sich auf Kopien der lokalen Variablen, die am Aufrufpunkt des Lambda-Ausdrucks erstellt wurden.
• [Erfassungsliste]: explizite Erfassung; Die Erfassungsliste ist die Liste der Namen lokaler Variablen, die erfasst werden sollen (dh im Objekt gespeichert sind), nach Referenz oder Wert. Variablen mit Namen vor & werden als Referenz erfasst. Andere Variablen werden nach Wert erfasst. Eine Erfassungsliste kann auch diese und Namen enthalten, gefolgt von ... als Elemente.
• [&, Erfassungsliste] : Erfassen Sie implizit alle lokalen Variablen mit Namen, die nicht in der Liste aufgeführt sind. Die Erfassungsliste kann dies enthalten. Aufgelistete Namen dürfen nicht vor & stehen. In der Erfassungsliste genannte Variablen werden nach Wert erfasst.
• [=, Erfassungsliste] : Erfasst implizit alle lokalen Variablen mit Namen, die nicht in der Liste aufgeführt sind, nach Wert. Die Erfassungsliste kann dies nicht enthalten. Vor den aufgeführten Namen muss & stehen. In der Erfassungsliste genannte Variablen werden als Referenz erfasst.
Beachten Sie, dass ein lokaler Name, dem & vorangestellt ist, immer als Referenz erfasst wird und ein lokaler Name, dem & nicht vorangestellt ist, immer als Wert erfasst wird. Nur die Erfassung durch Referenz ermöglicht das Ändern von Variablen in der aufrufenden Umgebung.
Schöne Erklärung. Mit bereichsbasierten for-Schleifen können Sie Lambdas vermeiden und den Code verkürzenfor (int x : v) { if (x % m == 0) os << x << '\n';}
Dietrich Baumgarten
2
Nun, eine praktische Anwendung, die ich herausgefunden habe, ist das Reduzieren des Kesselplattencodes. Zum Beispiel:
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);//... }
Ohne Lambda müssen Sie möglicherweise etwas für verschiedene bsizeFälle tun . Natürlich könnten Sie eine Funktion erstellen, aber was ist, wenn Sie die Verwendung im Rahmen der Soul-User-Funktion einschränken möchten? Die Natur von Lambda erfüllt diese Anforderung und ich benutze sie für diesen Fall.
Die Lambdas in c ++ werden als "on the go available function" behandelt. Ja, es ist buchstäblich unterwegs. Sie definieren es. benutze es; und wenn der übergeordnete Funktionsumfang beendet ist, ist die Lambda-Funktion weg.
Ich werde beschreiben, was nicht da ist, aber für jeden C ++ - Programmierer unbedingt zu wissen
Lambda soll nicht überall verwendet werden und jede Funktion kann nicht durch Lambda ersetzt werden. Es ist auch nicht das schnellste im Vergleich zur normalen Funktion. weil es einige Gemeinkosten hat, die von Lambda gehandhabt werden müssen.
In einigen Fällen wird dies sicherlich dazu beitragen, die Anzahl der Zeilen zu verringern. Es kann grundsätzlich für den Codeabschnitt verwendet werden, der einmal oder mehrmals in derselben Funktion aufgerufen wird, und dieser Code wird an keiner anderen Stelle benötigt, sodass Sie eine eigenständige Funktion dafür erstellen können.
Unten sehen Sie das grundlegende Beispiel für Lambda und was im Hintergrund passiert.
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;}
Wie Sie sehen können, wird der Overhead hinzugefügt, wenn Sie ihn verwenden. Es ist also keine gute Idee, sie überall zu verwenden. Es kann an Orten verwendet werden, an denen sie anwendbar sind.
Ja, es ist buchstäblich unterwegs. Sie definieren es. benutze es; und wenn der übergeordnete Funktionsumfang beendet ist, ist die Lambda-Funktion weg . Was ist, wenn die Funktion das Lambda an den Aufrufer zurückgibt?
Nawaz
1
Es ist auch nicht das schnellste im Vergleich zur normalen Funktion. weil es einige Gemeinkosten hat, die von Lambda gehandhabt werden müssen. Haben Sie jemals wirklich jede Benchmark laufen diese Behauptung zu unterstützen ? Im Gegenteil, Lambda + -Vorlagen erzeugen häufig den schnellstmöglichen Code.
Sie können ein const-Mitglied Ihrer Klasse mit einem Aufruf einer Funktion initialisieren, die ihren Wert festlegt, indem Sie ihre Ausgabe als Ausgabeparameter zurückgeben.
Dies kann auch mit einer einfachen Funktion erfolgen. Dies ist sogar das, was die akzeptierte Antwort auf die Frage, mit der Sie verlinkt haben, vorschreibt.
Antworten:
Das Problem
C ++ enthält nützliche generische Funktionen wie
std::for_each
undstd::transform
, die sehr praktisch sein können. Leider kann die Verwendung auch sehr umständlich sein, insbesondere wenn der Funktor, den Sie anwenden möchten, für die jeweilige Funktion eindeutig ist.Wenn Sie nur
f
einmal und an diesem bestimmten Ort verwenden, scheint es übertrieben, eine ganze Klasse zu schreiben, nur um etwas Triviales und Einmaliges zu tun.In C ++ 03 könnten Sie versucht sein, Folgendes zu schreiben, um den Funktor lokal zu halten:
Dies ist jedoch nicht zulässig und
f
kann in C ++ 03 nicht an eine Vorlagenfunktion übergeben werden.Die neue Lösung
In C ++ 11 werden Lambdas eingeführt, mit denen Sie einen anonymen Inline-Funktor schreiben können, der den ersetzt
struct f
. Für kleine einfache Beispiele kann dies sauberer zu lesen sein (es hält alles an einem Ort) und möglicherweise einfacher zu warten, zum Beispiel in der einfachsten Form:Lambda-Funktionen sind nur syntaktischer Zucker für anonyme Funktoren.
Rückgabetypen
In einfachen Fällen wird für Sie der Rückgabetyp des Lambda abgeleitet, z.
Wenn Sie jedoch anfangen, komplexere Lambdas zu schreiben, werden Sie schnell auf Fälle stoßen, in denen der Rückgabetyp vom Compiler nicht abgeleitet werden kann, z.
Um dies zu beheben, können Sie explizit einen Rückgabetyp für eine Lambda-Funktion angeben, indem Sie Folgendes verwenden
-> T
:Variablen "erfassen"
Bisher haben wir nichts anderes verwendet als das, was an das Lambda darin übergeben wurde, aber wir können auch andere Variablen innerhalb des Lambda verwenden. Wenn Sie auf andere Variablen zugreifen möchten, können Sie die Capture-Klausel (die
[]
des Ausdrucks) verwenden, die in diesen Beispielen bisher nicht verwendet wurde, z.Sie können sowohl von Referenz- und Wert erfassen, die Sie verwenden können angeben
&
und=
jeweils:[&epsilon]
Erfassung durch Referenz[&]
erfasst alle im Lambda verwendeten Variablen als Referenz[=]
Erfasst alle im Lambda verwendeten Variablen nach Wert[&, epsilon]
Erfasst Variablen wie mit [&], aber epsilon nach Wert[=, &epsilon]
erfasst Variablen wie mit [=], jedoch epsilon als ReferenzDie Generierung
operator()
erfolgtconst
standardmäßig mit der Folge, dass die Erfassung standardmäßig erfolgt,const
wenn Sie darauf zugreifen. Dies hat zur Folge, dass jeder Aufruf mit derselben Eingabe dasselbe Ergebnis liefert. Sie können jedoch das Lambda markieren, ummutable
zu fordern, dass dasoperator()
erzeugte nicht vorhanden istconst
.quelle
const
immer ...()
- es wird als Lambda mit null Argumenten übergeben, aber da() const
es nicht mit dem Lambda übereinstimmt, sucht es nach einer Typkonvertierung, die dies ermöglicht, einschließlich impliziter Umwandlung -to-function-pointer und ruft das dann auf! Hinterhältig!std::function<double(int, bool)> f = [](int a, bool b) -> double { ... };
Normalerweise lassen wir den Compiler den Typ ableiten:auto f = [](int a, bool b) -> double { ... };
(und vergessen Sie nicht#include <functional>
)return d < 0.00001 ? 0 : d;
garantiert doppelt zurückgegeben wird, wenn einer der Operanden eine ganzzahlige Konstante ist (dies liegt an einer impliziten Heraufstufungsregel des Operators ?:, Bei der der 2. und 3. Operand durch die übliche Arithmetik gegeneinander abgewogen werden Konvertierungen, egal welche ausgewählt wird). Ein Wechsel zu0.0 : d
würde das Beispiel vielleicht leichter verständlich machen.Was ist eine Lambda-Funktion?
Das C ++ - Konzept einer Lambda-Funktion stammt aus dem Lambda-Kalkül und der funktionalen Programmierung. Ein Lambda ist eine unbenannte Funktion, die (in der eigentlichen Programmierung, nicht in der Theorie) für kurze Codeausschnitte nützlich ist, die nicht wiederverwendet werden können und deren Benennung nicht wert ist.
In C ++ ist eine Lambda-Funktion wie folgt definiert
oder in all seiner Pracht
[]
ist die Erfassungsliste,()
die Argumentliste und{}
der Funktionskörper.Die Erfassungsliste
Die Erfassungsliste definiert, was von außerhalb des Lambda innerhalb des Funktionskörpers verfügbar sein soll und wie. Es kann entweder sein:
Sie können eine der oben genannten Optionen in einer durch Kommas getrennten Liste mischen
[x, &y]
.Die Argumentliste
Die Argumentliste ist dieselbe wie in jeder anderen C ++ - Funktion.
Der Funktionskörper
Der Code, der ausgeführt wird, wenn das Lambda tatsächlich aufgerufen wird.
Abzug vom Rückgabetyp
Wenn ein Lambda nur eine return-Anweisung hat, kann der return-Typ weggelassen werden und hat den impliziten Typ von
decltype(return_statement)
.Veränderlich
Wenn ein Lambda als veränderlich markiert ist (z. B.
[]() mutable { }
), darf es die Werte mutieren, die durch den Wert erfasst wurden.Anwendungsfälle
Die durch den ISO-Standard definierte Bibliothek profitiert stark von Lambdas und erhöht die Benutzerfreundlichkeit um mehrere Balken, da Benutzer ihren Code jetzt nicht mehr mit kleinen Funktoren in einem zugänglichen Bereich überladen müssen.
C ++ 14
In C ++ 14 wurden Lambdas um verschiedene Vorschläge erweitert.
Initialisierte Lambda-Aufnahmen
Ein Element der Erfassungsliste kann jetzt mit initialisiert werden
=
. Dies ermöglicht das Umbenennen von Variablen und das Erfassen durch Verschieben. Ein Beispiel aus dem Standard:und eine aus Wikipedia, die zeigt, wie man mit
std::move
:Generische Lambdas
Lambdas können jetzt generisch sein (
auto
wäre hier äquivalent,T
wennT
sich irgendwo im umgebenden Bereich ein Typvorlagenargument befindet ):Verbesserter Abzug vom Rückgabetyp
C ++ 14 erlaubt abgeleitete Rückgabetypen für jede Funktion und beschränkt sie nicht auf Funktionen des Formulars
return expression;
. Dies gilt auch für Lambdas.quelle
r = &x; r += 2;
, dies geschieht jedoch mit dem ursprünglichen Wert von 4.Lambda-Ausdrücke werden normalerweise verwendet, um Algorithmen zu kapseln, damit sie an eine andere Funktion übergeben werden können. Allerdings ist es möglich , eine Lambda unmittelbar nach Definition auszuführen :
ist funktional äquivalent zu
Dies macht Lambda-Ausdrücke zu einem leistungsstarken Werkzeug für die Umgestaltung komplexer Funktionen . Sie beginnen, indem Sie einen Codeabschnitt wie oben gezeigt in eine Lambda-Funktion einschließen. Der Prozess der expliziten Parametrisierung kann dann nach jedem Schritt schrittweise mit Zwischentests durchgeführt werden. Sobald Sie den Codeblock vollständig parametrisiert haben (wie durch das Entfernen des Codeblocks gezeigt
&
), können Sie den Code an einen externen Speicherort verschieben und zu einer normalen Funktion machen.Ebenso können Sie Lambda-Ausdrücke verwenden, um Variablen basierend auf dem Ergebnis eines Algorithmus zu initialisieren ...
Als eine Möglichkeit , Ihre Programmlogik der Partitionierung , könnten Sie sogar finden es sinnvoll , einen Lambda - Ausdruck als Argument für einen anderen Lambda - Ausdruck passieren ...
Mit Lambda-Ausdrücken können Sie auch benannte verschachtelte Funktionen erstellen. Dies kann eine bequeme Möglichkeit sein, doppelte Logik zu vermeiden. Die Verwendung von benannten Lambdas ist auch für die Augen etwas einfacher (im Vergleich zu anonymen Inline-Lambdas), wenn eine nicht triviale Funktion als Parameter an eine andere Funktion übergeben wird. Hinweis: Vergessen Sie das Semikolon nach der schließenden geschweiften Klammer nicht.
Wenn die nachfolgende Profilerstellung einen erheblichen Initialisierungsaufwand für das Funktionsobjekt ergibt, können Sie dies als normale Funktion umschreiben.
quelle
if
Aussagen funktioniert :if ([i]{ for (char j : i) if (!isspace(j)) return false ; return true ; }()) // i is all whitespace
Vorausgesetzt, esi
handelt sich um einstd::string
[](){}();
.(lambda: None)()
Syntax von Python ist viel besser lesbar.main() {{{{((([](){{}}())));}}}}
Antworten
F: Was ist ein Lambda-Ausdruck in C ++ 11?
A: Unter der Haube ist es das Objekt einer automatisch generierten Klasse mit Überladungsoperator () const . Ein solches Objekt wird als Closure bezeichnet und vom Compiler erstellt. Dieses 'Closure'-Konzept kommt dem Bind-Konzept aus C ++ 11 nahe. Aber Lambdas generieren normalerweise besseren Code. Und Anrufe durch Verschlüsse ermöglichen ein vollständiges Inlining.
F: Wann würde ich einen verwenden?
A: Um "einfache und kleine Logik" zu definieren und den Compiler zu bitten, führen Sie die Generierung aus der vorherigen Frage durch. Sie geben einem Compiler einige Ausdrücke, die Sie in operator () haben möchten. Alle anderen Sachen, die der Compiler für Sie generiert.
F: Welche Klasse von Problemen lösen sie, die vor ihrer Einführung nicht möglich waren?
A: Es ist eine Art Syntaxzucker wie das Überladen von Operatoren anstelle von Funktionen für benutzerdefinierte Add-, Subrtact- Operationen ... Es werden jedoch mehr Zeilen nicht benötigten Codes gespeichert , um 1-3 Zeilen echte Logik in einige Klassen usw. zu verpacken! Einige Ingenieure sind der Meinung, dass bei einer geringeren Anzahl von Zeilen die Wahrscheinlichkeit geringer ist, dass Fehler auftreten (ich denke auch).
Anwendungsbeispiel
Extras über Lambdas, nicht in Frage gestellt. Ignorieren Sie diesen Abschnitt, wenn Sie nicht interessiert sind
1. Erfasste Werte. Was können Sie erfassen
1.1. Sie können auf eine Variable mit statischer Speicherdauer in Lambdas verweisen. Sie alle werden gefangen genommen.
1.2. Sie können Lambda verwenden, um Werte "nach Wert" zu erfassen. In diesem Fall werden erfasste Variablen in das Funktionsobjekt kopiert (Abschluss).
1.3. Sie können als Referenz erfassen. & - bedeutet in diesem Zusammenhang Referenz, keine Zeiger.
1.4. Es gibt eine Notation, um alle nicht statischen Variablen nach Wert oder Referenz zu erfassen
1.5. Es gibt eine Notation, um alle nicht statischen Variablen nach Wert oder Referenz zu erfassen und smth anzugeben. Mehr. Beispiele: Erfassen Sie alle nicht statischen Variablen nach Wert, aber nach Referenzerfassung Param2
Erfassen Sie alle nicht statischen Variablen anhand der Referenz, aber anhand der Werterfassung Param2
2. Geben Sie den Typabzug zurück
2.1. Der Lambda-Rückgabetyp kann abgeleitet werden, wenn Lambda ein Ausdruck ist. Oder Sie können es explizit angeben.
Wenn Lambda mehr als einen Ausdruck hat, muss der Rückgabetyp über den nachfolgenden Rückgabetyp angegeben werden. Eine ähnliche Syntax kann auch auf automatische Funktionen und Elementfunktionen angewendet werden
3. Erfasste Werte. Was Sie nicht erfassen können
3.1. Sie können nur lokale Variablen erfassen, keine Mitgliedsvariable des Objekts.
4. versUmwandlungen
4.1 !! Lambda ist kein Funktionszeiger und keine anonyme Funktion, aber lambdas ohne Erfassung können implizit in einen Funktionszeiger konvertiert werden.
ps
Weitere Informationen zur Lambda-Grammatik finden Sie im Arbeitsentwurf für die Programmiersprache C ++ # 337, 16.01.2012, 5.1.2. Lambda Expressions, S.88
In C ++ 14 wurde die zusätzliche Funktion "Init Capture" hinzugefügt. Es ermöglicht die willkürliche Deklaration von Mitgliedern der Schließungsdaten:
quelle
[&,=Param2](int arg1){}
scheint keine gültige Syntax zu sein. Die richtige Form wäre[&,Param2](int arg1){}
Eine Lambda-Funktion ist eine anonyme Funktion, die Sie inline erstellen. Es kann Variablen erfassen, wie einige erklärt haben (z. B. http://www.stroustrup.com/C++11FAQ.html#lambda ), es gibt jedoch einige Einschränkungen. Wenn es beispielsweise eine solche Rückrufschnittstelle gibt,
Sie können eine Funktion an Ort und Stelle schreiben, um sie wie die unten verwendete zu verwenden:
Aber das kannst du nicht machen:
aufgrund von Einschränkungen im C ++ 11-Standard. Wenn Sie Captures verwenden möchten, müssen Sie sich auf die Bibliothek und verlassen
(oder einen anderen STL-Bibliotheks-ähnlichen Algorithmus, um ihn indirekt abzurufen) und dann mit std :: function zu arbeiten, anstatt normale Funktionen als Parameter wie diesen zu übergeben:
quelle
apply
es eine Vorlage wäre, die einen Funktor akzeptiert, würde sie funktionierenEine der besten Erklärungen dafür
lambda expression
gibt der Autor von C ++ Bjarne Stroustrup in seinem Buch***The C++ Programming Language***
Kapitel 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?
Hier denke ich, dass jede Aktion, die mit Lambda-Ausdruck ausgeführt wird, ohne sie gelöst werden kann, aber mit viel mehr Code und viel größerer Komplexität. Lambda-Ausdruck Dies ist die Art der Optimierung Ihres Codes und eine Möglichkeit, ihn attraktiver zu machen. So traurig von Stroustup:
Some examples
über Lambda-Ausdruck
oder über Funktion
oder auch
Wenn du brauchst, kannst du
lambda expression
wie folgt benennen :Oder nehmen Sie eine andere einfache Probe an
wird als nächstes generieren
[]
- Dies ist eine Erfassungsliste oderlambda introducer
: Wennlambdas
kein Zugriff auf die lokale Umgebung erforderlich ist, können wir sie verwenden.Zitat aus dem Buch:
Additional
Lambda expression
FormatZusätzliche Referenzen:
quelle
for (int x : v) { if (x % m == 0) os << x << '\n';}
Nun, eine praktische Anwendung, die ich herausgefunden habe, ist das Reduzieren des Kesselplattencodes. Zum Beispiel:
Ohne Lambda müssen Sie möglicherweise etwas für verschiedene
bsize
Fälle tun . Natürlich könnten Sie eine Funktion erstellen, aber was ist, wenn Sie die Verwendung im Rahmen der Soul-User-Funktion einschränken möchten? Die Natur von Lambda erfüllt diese Anforderung und ich benutze sie für diesen Fall.quelle
Die Lambdas in c ++ werden als "on the go available function" behandelt. Ja, es ist buchstäblich unterwegs. Sie definieren es. benutze es; und wenn der übergeordnete Funktionsumfang beendet ist, ist die Lambda-Funktion weg.
c ++ führte es in c ++ 11 ein und jeder begann es wie an jedem möglichen Ort zu benutzen. Das Beispiel und was Lambda ist, finden Sie hier https://en.cppreference.com/w/cpp/language/lambda
Ich werde beschreiben, was nicht da ist, aber für jeden C ++ - Programmierer unbedingt zu wissen
Lambda soll nicht überall verwendet werden und jede Funktion kann nicht durch Lambda ersetzt werden. Es ist auch nicht das schnellste im Vergleich zur normalen Funktion. weil es einige Gemeinkosten hat, die von Lambda gehandhabt werden müssen.
In einigen Fällen wird dies sicherlich dazu beitragen, die Anzahl der Zeilen zu verringern. Es kann grundsätzlich für den Codeabschnitt verwendet werden, der einmal oder mehrmals in derselben Funktion aufgerufen wird, und dieser Code wird an keiner anderen Stelle benötigt, sodass Sie eine eigenständige Funktion dafür erstellen können.
Unten sehen Sie das grundlegende Beispiel für Lambda und was im Hintergrund passiert.
Benutzercode:
Wie compile es erweitert:
Wie Sie sehen können, wird der Overhead hinzugefügt, wenn Sie ihn verwenden. Es ist also keine gute Idee, sie überall zu verwenden. Es kann an Orten verwendet werden, an denen sie anwendbar sind.
quelle
Ein Problem, das es löst: Code einfacher als Lambda für einen Aufruf im Konstruktor, der eine Ausgabeparameterfunktion zum Initialisieren eines const-Elements verwendet
Sie können ein const-Mitglied Ihrer Klasse mit einem Aufruf einer Funktion initialisieren, die ihren Wert festlegt, indem Sie ihre Ausgabe als Ausgabeparameter zurückgeben.
quelle