Lambdaausdrücke in C++
In C++11 und höher ist ein Lambda-Ausdruck , der häufig als Lambda bezeichnet wird, eine bequeme Möglichkeit, ein anonymes Funktionsobjekt (ein Schließen) direkt an der Stelle zu definieren, an der es aufgerufen oder als Argument an eine Funktion übergeben wird. In der Regel werden Lambdas verwendet, um einige Codezeilen zu kapseln, die an Algorithmen oder asynchrone Funktionen übergeben werden. In diesem Artikel wird definiert, was Lambdas sind, und sie mit anderen Programmiertechniken verglichen. Es beschreibt ihre Vorteile und enthält einige grundlegende Beispiele.
Verwandte Artikel
- Lambda-Ausdrücke im Vergleich zu Funktionsobjekten
- Arbeiten mit Lambda-Ausdrücken
- constexpr Lambda-Ausdrücke
Teile eines Lambda-Ausdrucks
Hier ist eine einfache Lambda-Funktion, die als drittes Argument an die std::sort()
Funktion übergeben wird:
#include <algorithm>
#include <cmath>
void abssort(float* x, unsigned n) {
std::sort(x, x + n,
// Lambda expression begins
[](float a, float b) {
return (std::abs(a) < std::abs(b));
} // end of lambda expression
);
}
Diese Abbildung zeigt die Teile der Lambda-Syntax:
Das Lambda-Ausdrucksbeispiel ist [=]() mutable throw() -> int { return x+y; } [=] ist die Aufnahmeklausel; auch als Lambda-Einführung in der C++-Spezifikation bezeichnet. Die Klammer ist für die Parameterliste vorgesehen. Die änderbare Schlüsselwort (keyword) ist optional. throw() ist die optionale Ausnahmespezifikation. -> int ist der optionale nachfolgende Rückgabetyp. Der Lambda-Text besteht aus der Anweisung innerhalb der geschweiften geschweiften Klammern oder zurückgeben x+y; Diese werden im Anschluss an das Bild ausführlicher erläutert.
Capture-Klausel (Auch bekannt als Lambda-Einführung in der C++-Spezifikation.)
Parameterliste Optional. (Auch als Lambda-Deklarator bezeichnet)
änderbare Spezifikation Optional.
Ausnahmespezifikation Optional.
Nachgestellter Rückgabetyp Optional.
Lambda-Textkörper.
Capture-Klausel
Mit einer Lambda-Funktion können neue Variablen im Textkörper (in C++14) eingeführt werden, und sie kann auch auf Variablen aus dem umgebenden Bereich zugreifen oder diese erfassen. Eine Lambda-Funktion beginnt mit der Aufnahmeklausel. Es gibt an, welche Variablen erfasst werden, und ob die Erfassung nach Wert oder referenziert ist. Auf Variablen mit dem Präfix kaufmännisches Und-Zeichen (&
) wird durch Verweis und Variablen zugegriffen, auf die sie nicht über einen Wert zugegriffen wird.
Eine leere Erfassungsklausel ([ ]
) gibt an, dass der Lambda-Text auf keine Variablen im einschließenden Bereich zugreift.
Sie können einen Capture-Default-Modus verwenden, um anzugeben, wie externe Variablen erfasst werden, auf die im Lambda-Textkörper verwiesen wird: [&]
bedeutet, dass alle Variablen, auf die Sie verweisen, durch Verweis erfasst werden, und [=]
bedeutet, dass sie nach Wert erfasst werden. Sie können einen Standarderfassungsmodus verwenden, und dann den entgegengesetzten Modus explizit für bestimmte Variablen angeben. Wenn beispielsweise der Lambda-Text auf die externe Variable total
nach Wert zugreift und auf die externe Variable factor
nach Wert, dann sind die folgenden Erfassungsklauseln gleichwertig:
[&total, factor]
[factor, &total]
[&, factor]
[=, &total]
Nur Variablen, die im Lambda-Textkörper Erwähnung werden erfasst, wenn ein Aufnahmestandard verwendet wird.
Wenn eine Aufnahmeklausel einen Capture-Standard &
enthält, kann kein Bezeichner in einer Erfassung dieser Aufnahmeklausel das Formular &identifier
aufweisen. Ebenso kann, wenn die Aufnahmeklausel einen Capture-Standard =
enthält, keine Erfassung dieser Aufnahmeklausel das Formular =identifier
aufweisen. Ein Bezeichner oder this
kann nicht mehr als einmal in einer Aufnahmeklausel angezeigt werden. Der folgende Codeausschnitt veranschaulicht einige Beispiele:
struct S { void f(int i); };
void S::f(int i) {
[&, i]{}; // OK
[&, &i]{}; // ERROR: i preceded by & when & is the default
[=, this]{}; // ERROR: this when = is the default
[=, *this]{ }; // OK: captures this by value. See below.
[i, i]{}; // ERROR: i repeated
}
Eine Aufnahme, gefolgt von einer Auslassungspunkte, ist eine Packerweiterung, wie in diesem variadischen Vorlagenbeispiel gezeigt:
template<class... Args>
void f(Args... args) {
auto x = [args...] { return g(args...); };
x();
}
Um Lambda-Ausdrücke im Textkörper einer Klassenmemberfunktion zu verwenden, übergeben Sie den this
Zeiger an die Aufnahmeklausel, um Zugriff auf die Memberfunktionen und Datenmember der eingeschlossenen Klasse zu ermöglichen.
Visual Studio 2017, Version 15.3 und höher (verfügbar im /std:c++17
Modus und höher): Der this
Zeiger kann durch Angabe *this
in der Aufnahmeklausel durch Wert erfasst werden. Die Erfassung nach Wert kopiert die gesamte Schließung an jede Anrufwebsite, auf der die Lambda-Funktion aufgerufen wird. (Ein Schließen ist das anonyme Funktionsobjekt, das den Lambda-Ausdruck kapselt.) Die Erfassung nach Wert ist nützlich, wenn die Lambda-Funktion in parallelen oder asynchronen Vorgängen ausgeführt wird. Es ist besonders nützlich für bestimmte Hardwarearchitekturen, z. B. NUMA.
Ein Beispiel, das zeigt, wie Lambda-Ausdrücke mit Klassenmememmfunktionen verwendet werden, finden Sie unter "Beispiel: Verwenden eines Lambda-Ausdrucks in einer Methode" in Beispielen für Lambda-Ausdrücke.
Wenn Sie die Aufnahmeklausel verwenden, sollten Sie diese Punkte berücksichtigen, insbesondere, wenn Sie Lambdas mit Multithreading verwenden:
Referenzerfassungen können verwendet werden, um Variablen außerhalb zu ändern, Aber Werteerfassungen können nicht. (
mutable
ermöglicht das Ändern von Kopien, aber nicht originalen.)Referenzerfassungen spiegeln Aktualisierungen von Variablen außerhalb wider, werte erfassen jedoch nicht.
Verweiserfassungen führen eine Lebenszeitabhängigkeit ein, während Werterfassungen keine Lebenszeitabhängigkeiten haben. Es ist besonders wichtig, wenn die Lambda-Funktion asynchron ausgeführt wird. Wenn Sie einen lokalen Verweis in einer asynchronen Lambda-Funktion erfassen, kann diese lokal leicht nach der Ausführung der Lambda-Funktion verschwunden sein. Ihr Code könnte zur Laufzeit einen Zugriffsverstoß verursachen.
Generalisierte Erfassung (C++14)
In C++14 können Sie neue Variablen in der Aufnahmeklausel einführen und initialisieren, ohne dass diese Variablen im eingeschlossenen Bereich der Lambda-Funktion vorhanden sein müssen. Die Initialisierung kann mit einem beliebigen Ausdruck ausgedrückt werden; der Typ der neuen Variablen wird von dem durch den Ausdruck genierten Typ abgeleitet. Mit diesem Feature können Sie move-only-Variablen (z std::unique_ptr
. B. ) aus dem umgebenden Bereich erfassen und in einer Lambda-Funktion verwenden.
pNums = make_unique<vector<int>>(nums);
//...
auto a = [ptr = move(pNums)]()
{
// use ptr
};
Parameterliste
Lambdas können Sowohl Variablen erfassen als auch Eingabeparameter akzeptieren. Eine Parameterliste (Lambda-Deklarator in der Standardsyntax) ist optional und in den meisten Aspekten ähnelt der Parameterliste für eine Funktion.
auto y = [] (int first, int second)
{
return first + second;
};
Wenn der Parametertyp generisch ist, können Sie in C++14 den auto
Schlüsselwort (keyword) als Typbezeichner verwenden. Dieser Schlüsselwort (keyword) weist den Compiler an, den Funktionsaufrufoperator als Vorlage zu erstellen. Jede Instanz auto
in einer Parameterliste entspricht einem eindeutigen Typparameter.
auto y = [] (auto first, auto second)
{
return first + second;
};
Ein Lambdaausdruck kann einen anderen Lambdaausdruck als Argument übernehmen. Weitere Informationen finden Sie unter "Lambda-Ausdrücke mit höherer Reihenfolge" im Artikel Beispiele für Lambda-Ausdrücke.
Da eine Parameterliste optional ist, können Sie die leeren Klammern weglassen, wenn Sie keine Argumente an den Lambda-Ausdruck übergeben und dessen Lambda-Deklarator keine Ausnahmespezifikation, nachgestellten Rückgabetyp oder mutable
.
Veränderbare Spezifikation
In der Regel ist der Funktionsaufrufoperator einer Lambda-Funktion konstant, aber die Verwendung der mutable
Schlüsselwort (keyword) bricht dies ab. Es werden keine veränderbaren Datenmember erzeugt. Die mutable
Spezifikation ermöglicht es dem Textkörper eines Lambda-Ausdrucks, Variablen zu ändern, die nach Wert erfasst werden. Einige der Beispiele weiter unten in diesem Artikel zeigen, wie sie verwendet werden mutable
.
Ausnahmespezifikation
Mit der noexcept
Ausnahmespezifikation können Sie angeben, dass der Lambda-Ausdruck keine Ausnahmen auslöst. Wie bei normalen Funktionen generiert der Microsoft C++-Compiler die Warnung C4297 , wenn ein Lambda-Ausdruck die noexcept
Ausnahmespezifikation deklariert und der Lambda-Textkörper eine Ausnahme auslöst, wie hier gezeigt:
// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
[]() noexcept { throw 5; }();
}
Weitere Informationen finden Sie unter Ausnahmespezifikationen (Throw).For more information, see Exception specifications (throw).
Rückgabetyp
Der Rückgabetyp von Lambda-Ausdrücken wird automatisch hergeleitet. Sie müssen die auto
Schlüsselwort (keyword) nicht verwenden, es sei denn, Sie geben einen nachgestellten Rückgabetyp an. Der nachfolgende Rückgabetyp ähnelt dem Rückgabetypteil einer normalen Funktion oder Memberfunktion. Der Rückgabetyp folgt jedoch auf die Parameterliste, und das Schlüsselwort „trailing-return-type ->
“ muss vor dem Rückgabetyp angegeben werden.
Sie können den Rückgabetypteil eines Lambda-Ausdrucks weglassen, wenn der Lambda-Textkörper nur eine Rückgabe-Anweisung enthält. Oder wenn der Ausdruck keinen Wert zurückgibt. Wenn der Lambda-Text aus einer einzelnen Rückgabeanweisung besteht, leitet der Compiler den Rückgabetyp vom Typ des Rückgabeausdrucks ab. Andernfalls leitet der Compiler den Rückgabetyp als void
. Betrachten Sie die folgenden Beispielcodeausschnitte, die dieses Prinzip veranschaulichen:
auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
// return type from braced-init-list isn't valid
Ein Lambdaausdruck kann einen anderen Lambdaausdruck als Rückgabewert erzeugen. Weitere Informationen finden Sie unter "Lambda-Ausdrücke mit höherer Reihenfolge" in Beispielen für Lambda-Ausdrücke.
Lambda-Textkörper
Der Lambda-Text eines Lambda-Ausdrucks ist eine zusammengesetzte Anweisung. Sie kann alles enthalten, was im Textkörper einer normalen Funktion oder Mitgliedsfunktion zulässig ist. Der Text einer gewöhnlichen Funktion und eines Lambdaausdrucks kann auf die folgenden Variablenarten zugreifen:
Aus dem einschließenden Bereich erfasste Variablen, wie zuvor beschrieben.
Parameter.
Lokal deklarierte Variablen.
Klassendatenmember, wenn sie innerhalb einer Klasse deklariert und
this
erfasst werden.Jede Variable mit statischer Speicherdauer , z. B. globale Variablen.
Das folgende Beispiel enthält einen Lambdaausdruck, welcher explizit die Variable n
nach ihrem Wert erfasst und implizit die Variable m
als Verweis erfasst:
// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;
int main()
{
int m = 0;
int n = 0;
[&, n] (int a) mutable { m = ++n + a; }(4);
cout << m << endl << n << endl;
}
5
0
Da die Variable n
als Wert erfasst wird, bleibt der Wert 0
nach dem Aufruf des Lambda-Ausdrucks erhalten. Die mutable
Spezifikation ermöglicht n
die Änderung innerhalb der Lambda-Funktion.
Ein Lambda-Ausdruck kann nur Variablen erfassen, die die automatische Speicherdauer aufweisen. Sie können jedoch Variablen verwenden, die statische Speicherdauer im Textkörper eines Lambda-Ausdrucks aufweisen. Im folgenden Beispiel wird die Funktion generate
und ein Lambdaausdruck verwendet, um jedem Element in einem vector
-Objekt einen Wert zuzuweisen. Der Lambda-Ausdruck ändert die statische Variable, um den Wert des nächsten Elements zu generieren.
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this isn't thread-safe and is shown for illustration only
}
Weitere Informationen finden Sie unter generieren.
Im folgenden Codebeispiel wird die Funktion aus dem vorherigen Beispiel verwendet, und es wird ein Beispiel für einen Lambda-Ausdruck hinzugefügt, der den C++-Standardbibliotheksalgorithmus verwendet generate_n
. Dieser Lambdaausdruck weist ein Element eines vector
-Objekts der Summe der vorangehenden zwei Elemente zu. Die mutable
Schlüsselwort (keyword) wird verwendet, damit der Textkörper des Lambda-Ausdrucks seine Kopien der externen Variablen x
ändern kann und y
die der Lambda-Ausdruck nach Wert erfasst. Da der Lambdaausdruck die originalen Variablen x
und y
als Wert erfasst, bleiben die Werte 1
, nachdem das Lambda ausgeführt wird.
// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <typename C> void print(const string& s, const C& c) {
cout << s;
for (const auto& e : c) {
cout << e << " ";
}
cout << endl;
}
void fillVector(vector<int>& v)
{
// A local static variable.
static int nextValue = 1;
// The lambda expression that appears in the following call to
// the generate function modifies and uses the local static
// variable nextValue.
generate(v.begin(), v.end(), [] { return nextValue++; });
//WARNING: this isn't thread-safe and is shown for illustration only
}
int main()
{
// The number of elements in the vector.
const int elementCount = 9;
// Create a vector object with each element set to 1.
vector<int> v(elementCount, 1);
// These variables hold the previous two elements of the vector.
int x = 1;
int y = 1;
// Sets each element in the vector to the sum of the
// previous two elements.
generate_n(v.begin() + 2,
elementCount - 2,
[=]() mutable throw() -> int { // lambda is the 3rd parameter
// Generate current value.
int n = x + y;
// Update previous two values.
x = y;
y = n;
return n;
});
print("vector v after call to generate_n() with lambda: ", v);
// Print the local variables x and y.
// The values of x and y hold their initial values because
// they are captured by value.
cout << "x: " << x << " y: " << y << endl;
// Fill the vector with a sequence of numbers
fillVector(v);
print("vector v after 1st call to fillVector(): ", v);
// Fill the vector with the next sequence of numbers
fillVector(v);
print("vector v after 2nd call to fillVector(): ", v);
}
vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34
x: 1 y: 1
vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9
vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18
Weitere Informationen finden Sie unter generate_n.
constexpr
Lambda-Ausdrücke
Visual Studio 2017, Version 15.3 und höher (im /std:c++17
Modus und höher verfügbar): Sie können einen Lambda-Ausdruck als constexpr
(oder in einem konstanten Ausdruck verwenden) deklarieren, wenn die Initialisierung der einzelnen erfassten oder eingeführten Datenmemm innerhalb eines konstanten Ausdrucks zulässig ist.
int y = 32;
auto answer = [y]() constexpr
{
int x = 10;
return y + x;
};
constexpr int Increment(int n)
{
return [n] { return n + 1; }();
}
Eine Lambda-Funktion ist implizit constexpr
, wenn ihr Ergebnis den Anforderungen einer constexpr
Funktion entspricht:
auto answer = [](int n)
{
return 32 + n;
};
constexpr int response = answer(10);
Wenn eine Lambda-Funktion implizit oder explizit constexpr
erfolgt, erzeugt die Konvertierung in einen Funktionszeiger eine constexpr
Funktion:
auto Increment = [](int n)
{
return n + 1;
};
constexpr int(*inc)(int) = Increment;
Microsoft-spezifisch
Lambdas werden in den folgenden vom ClR verwalteten Entitäten (Common Language Runtime) nicht unterstützt: ref class
, , , ref struct
, value class
oder value struct
.
Wenn Sie einen Microsoft-spezifischen Modifizierer wie z __declspec
. B. verwenden, können Sie ihn direkt hinter dem parameter-declaration-clause
. Beispiel:
auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };
Informationen dazu, ob ein bestimmter Modifizierer von Lambdas unterstützt wird, finden Sie im Artikel zum Modifizierer im Abschnitt "Microsoft-spezifische Modifizierer ".
Visual Studio unterstützt C++11 Standard Lambda-Funktionen und zustandslose Lambdas. Eine zustandslose Lambda-Funktion ist in einen Funktionszeiger konvertierbar, der eine beliebige Aufrufkonvention verwendet.
Siehe auch
C++-Programmiersprachenreferenz
Funktionsobjekte in der C++-Standardbibliothek
Funktionsaufruf
for_each
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Tickets als Feedbackmechanismus für Inhalte auslaufen lassen und es durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unter:Einreichen und Feedback anzeigen für