Was ist ein Lambda-Ausdruck in C++11?
Was ist ein Lambda-Ausdruck in C++11? Wann würde ich einen verwenden? Welche Art von Problemen lösen sie, die vor ihrer Einführung nicht möglich waren?
Ein paar Beispiele und Anwendungsfälle wären hilfreich.
1398
3
Das Problem
C++ enthält nützliche generische Funktionen wie
std::for_each
undstd::transform
, die sehr praktisch sein können. Leider können sie auch recht umständlich zu benutzen sein, besonders wenn der functor, den Sie anwenden möchten, nur für die jeweilige Funktion gilt.Wenn man
f
nur einmal und an dieser speziellen Stelle verwendet, scheint es übertrieben, eine ganze Klasse zu schreiben, nur um etwas Triviales und Einmaliges zu tun.In C++03 könnte man versucht sein, etwas wie das Folgende zu schreiben, um den Funktor lokal zu halten:
Dies ist jedoch nicht erlaubt, da
f
in C++03 nicht an eine Template-Funktion übergeben werden kann.Die neue Lösung
C++11 führt Lambdas ein, die es ermöglichen, einen anonymen Inline-Faktor zu schreiben, der das "Konstrukt f" ersetzt. Für kleine, einfache Beispiele kann dies sauberer zu lesen sein (alles bleibt an einem Ort) und möglicherweise einfacher zu pflegen, zum Beispiel in der einfachsten Form:
Lambda-Funktionen sind nur syntaktischer Zucker für anonyme Funktoren.
Rückgabetypen
In einfachen Fällen wird der Rückgabetyp des Lambdas für Sie hergeleitet, z.B.:
Wenn Sie jedoch anfangen, komplexere Lambdas zu schreiben, werden Sie schnell auf Fälle stoßen, in denen der Rückgabetyp nicht vom Compiler abgeleitet werden kann, z. B.:
Um dieses Problem zu lösen, können Sie explizit einen Rückgabetyp für eine Lambda-Funktion angeben, indem Sie
-> T
verwenden:"Erfassen" von Variablen
Bisher haben wir nur das verwendet, was dem Lambda innerhalb des Lambdas übergeben wurde, aber wir können auch andere Variablen innerhalb des Lambdas verwenden. Wenn Sie auf andere Variablen zugreifen wollen, können Sie die Capture-Klausel (das
[]
des Ausdrucks) verwenden, die in diesen Beispielen bisher nicht verwendet wurde, z.B.:Sie können sowohl über eine Referenz als auch über einen Wert erfassen, die Sie mit
&
bzw.=
angeben können:[&epsilon]
erfasst durch Referenz[&]
erfasst alle im Lambda verwendeten Variablen per Referenz[=]
erfasst alle Variablen, die im Lambda als Wert verwendet werden[&, epsilon]
erfasst Variablen wie mit [&], aber epsilon als Wert[=, &epsilon]
erfasst Variablen wie mit [=], aber epsilon als ReferenzDer erzeugte
operator()
ist standardmäßigconst
, was bedeutet, dass die Captures standardmäßigconst
sind, wenn man auf sie zugreift. Dies hat den Effekt, dass jeder Aufruf mit der gleichen Eingabe das gleiche Ergebnis liefert. Sie können jedoch das Lambda alsmutable
markieren, um zu verlangen, dass der erzeugteOperator()
nichtconst
ist.Was ist eine Lambda-Funktion?
Das C++-Konzept der Lambda-Funktion stammt aus dem Lambda-Kalkül und der funktionalen Programmierung. Ein Lambda ist eine unbenannte Funktion, die (in der tatsächlichen Programmierung, nicht in der Theorie) für kurze Codeschnipsel nützlich ist, die nicht wiederverwendet werden können und keine Benennung wert sind.
In C++ ist eine Lambda-Funktion wie folgt definiert
oder in seiner ganzen Pracht
[]
ist die Fangliste,()
die Argumentliste und{}
der Funktionskörper.Die Aufnahmeliste
Die Aufnahmeliste legt fest, was von außerhalb des Lambdas im Funktionsrumpf verfügbar sein soll und wie. Sie kann entweder sein:
Sie können jedes der oben genannten Elemente in einer durch Komma getrennten Liste
[x, &y]
mischen.Die Argumentliste
Die Argumentenliste ist die gleiche wie in jeder anderen C++-Funktion.
Der Funktionskörper
Der Code, der ausgeführt wird, wenn das Lambda tatsächlich aufgerufen wird.
Rückgabetyp Abzug
Wenn ein Lambda nur eine Rückgabeanweisung hat, kann der Rückgabetyp weggelassen werden und hat den impliziten Typ von
decltype(return_statement)
.Veränderlich
Wenn ein Lambda als veränderbar gekennzeichnet ist (z. B.
[]() mutable { }
), darf es die Werte, die durch value erfasst wurden, verändern.Anwendungsfälle
Die von der ISO-Norm definierte Bibliothek profitiert stark von Lambdas und erhöht die Benutzerfreundlichkeit um ein Vielfaches, da die Benutzer nun nicht mehr ihren Code mit kleinen Funktoren in einem zugänglichen Bereich überladen müssen.
C++14
In C++14 sind Lambdas durch verschiedene Vorschläge erweitert worden.
Initialisierte Lambda-Erfassungen
Ein Element der Capture-Liste kann nun mit
=
initialisiert werden. Dies ermöglicht die Umbenennung von Variablen und das Capturen durch Verschieben. Ein Beispiel aus dem Standard:und eines aus Wikipedia, das zeigt, wie man mit
std::move
einfangen kann:Generische Lambdas
Lambdas können nun generisch sein (
auto
wäre hier äquivalent zuT
, wenn T" ein Typvorlagenargument irgendwo im umgebenden Bereich wäre):Verbesserte Return Type Deduction
C++14 erlaubt die Ableitung von Rückgabetypen für jede Funktion und beschränkt sie nicht auf Funktionen der Form
Rückgabeausdruck;
. Dies wird auch auf Lambdas ausgeweitet.Lambda-Ausdrücke werden normalerweise verwendet, um Algorithmen zu kapseln, damit sie an eine andere Funktion übergeben werden können. Es ist jedoch möglich, einen Lambda-Ausdruck sofort nach seiner Definition auszuführen:
ist funktional äquivalent zu
Dies macht Lambda-Ausdrücke zu einem mächtigen Werkzeug für das Refactoring komplexer Funktionen. Sie beginnen damit, einen Codeabschnitt in eine Lambda-Funktion zu verpacken, wie oben gezeigt. Der Prozess der expliziten Parametrisierung kann dann schrittweise mit Zwischentests nach jedem Schritt durchgeführt werden. Sobald der Code-Block vollständig parametrisiert ist (wie durch das Entfernen des
&
demonstriert), können Sie den Code an eine externe Stelle verschieben und ihn zu einer normalen Funktion machen.In ähnlicher Weise können Sie Lambda-Ausdrücke verwenden, um Variablen auf der Grundlage des Ergebnisses eines Algorithmus zu initialisieren...
Als Möglichkeit der Partitionierung Ihrer Programmlogik kann es sogar nützlich sein, einen Lambda-Ausdruck als Argument an einen anderen Lambda-Ausdruck zu übergeben...
Mit Lambda-Ausdrücken können Sie auch benannte verschachtelte Funktionen erstellen, was eine bequeme Möglichkeit sein kann, doppelte Logik zu vermeiden. Die Verwendung von benannten Lambdas ist auch etwas übersichtlicher (im Vergleich zu anonymen Inline-Lambdas), wenn eine nicht-triviale Funktion als Parameter an eine andere Funktion übergeben wird. *Hinweis: Vergessen Sie nicht das Semikolon nach der schließenden geschweiften Klammer.
Wenn die anschließende Profilerstellung einen erheblichen Initialisierungs-Overhead für das Funktionsobjekt ergibt, sollten Sie diese Funktion als normale Funktion umschreiben.