In C++11 ist ein Lambda-Ausdruck (oft als „Lambda“ bezeichnet) eine praktische Möglichkeit, ein anonymes Funktionsobjekt an der Stelle zu definieren, an der es aufgerufen oder als Argument an eine Funktion übergeben wird. Lambdas werden normalerweise verwendet, um eine kleine Anzahl von Codezeilen zu kapseln, die an einen Algorithmus oder eine asynchrone Methode übergeben werden. Dieser Artikel definiert, was Lambdas sind, vergleicht Lambdas mit anderen Programmiertechniken, beschreibt ihre Vorteile und bietet ein einfaches Beispiel.
Teile eines Lambda-Ausdrucks
Der ISO C++-Standard zeigt ein einfaches Lambda, das als drittes Argument an die Funktion std::sort() ü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 ); }
Dieses Diagramm zeigt die Komponenten eines Lambda:
Capture-Klausel (in der C++-Spezifikation auch Lambda-Bootstrapping genannt.)
Parameterliste (optional). (auch Lambda-Deklarator genannt)
Variablenspezifikation (optional).
Ausnahmespezifikation (optional).
Nachgestellter Rückgabetyp (optional).
"Lambda-Körper"
Capture-Klausel
Lambda kann neue Variablen in seinen Körper einführen (in C++14), es kann auch darauf zugreifen (oder „einfangen“) Variablen im umgebenden Bereich. Ein Lambda beginnt mit einer Capture-Klausel (dem Lambda-Bootstrap in der Standardsyntax), die die zu erfassende Variable angibt und angibt, ob sie als Wert oder Referenz erfasst werden soll. Auf Variablen, denen ein kaufmännisches Und-Zeichen (&) vorangestellt ist, wird per Referenz zugegriffen, auf Variablen ohne dieses Präfix wird über den Wert zugegriffen.
Eine leere Capture-Klausel [] gibt an, dass der Hauptteil des Lambda-Ausdrucks nicht auf Variablen im umschließenden Bereich zugreift.
Der Standarderfassungsmodus (capture-default in der Standardsyntax) kann verwendet werden, um anzugeben, wie alle externen Variablen erfasst werden, auf die im Lambda verwiesen wird: [&] bedeutet, dass alle Variablen erfasst werden, auf die durch Referenz verwiesen wird, während [=] bedeutet, dass sie erfasst werden nach Wert. Sie können den Standarderfassungsmodus verwenden und dann explizit den entgegengesetzten Modus für eine bestimmte Variable angeben. Wenn der Lambda-Körper beispielsweise über Referenz auf die externe Variable total und über Wert auf den externen Variablenfaktor zugreift, ist die folgende Capture-Klausel äquivalent:
[&total, factor] [factor, &total] [&, factor] [factor, &] [=, &total] [&total, =]
Verwenden Sie „capture -default“. Nur im Lambda erwähnte Variablen werden erfasst.
Wenn eine Capture-Klausel „capture-default&“ enthält, kann kein Capture im Bezeichner dieser Capture-Klausel die Form & Bezeichner annehmen. Wenn eine Capture-Klausel ebenfalls „capture-default=“ enthält, kann die Erfassung der Capture-Klausel nicht die Form „= Bezeichner“ annehmen. Dieser Bezeichner darf nicht mehr als einmal in der Capture-Klausel vorkommen. Die folgenden Codeausschnitte geben 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 [i, i]{}; // ERROR: i repeated }
capture gefolgt von einem Auslassungszeichen ist eine Paketerweiterung, wie im folgenden Variadic-Vorlagenbeispiel gezeigt:
template<class... Args> void f(Args... args) { auto x = [args...] { return g(args...); }; x(); }
Um einen Lambda-Ausdruck im Hauptteil einer Klassenmethode zu verwenden, übergeben Sie den Zeiger this an die Capture-Klausel, um Zugriff auf die Methoden und Datenmitglieder der umschließenden Klasse zu ermöglichen. Ein Beispiel, das zeigt, wie Lambda-Ausdrücke mit Klassenmethoden verwendet werden, finden Sie unter „Beispiel: Verwenden von Lambda-Ausdrücken in Methoden“ in Beispiele für Lambda-Ausdrücke.
Bei Verwendung der Capture-Klausel wird empfohlen, die folgenden Punkte zu beachten (insbesondere bei Verwendung von Lambdas, die mehrere Threads verwenden):
Referenzerfassung kann zum Ändern externer Variablen verwendet werden, die Werterfassung kann diesen Vorgang jedoch nicht ausführen. (Eine veränderliche Variable ermöglicht die Änderung der Kopie, jedoch nicht des Originals.)
Die Referenzerfassung spiegelt Aktualisierungen externer Variablen wider, die Werterfassung jedoch nicht.
Die Referenzerfassung führt lebenslange Abhängigkeiten ein, während die Werterfassung keine lebenslangen Abhängigkeiten aufweist. Dies ist besonders wichtig, wenn das Lambda asynchron ausgeführt wird. Wenn Sie eine lokale Variable per Referenz in einem asynchronen Lambda erfassen, verschwindet die lokale Variable höchstwahrscheinlich, wenn das Lambda ausgeführt wird, was zu einer Laufzeitzugriffsverletzung führt.
Universelle Erfassung (C++14)
In C++14 können neue Variablen in der Capture-Klausel eingeführt und initialisiert werden, ohne dass diese Variablen im umschließenden Bereich der Lambda-Funktion vorhanden sind. Die Initialisierung kann durch einen beliebigen Ausdruck ausgedrückt werden, und der Typ der neuen Variablen wird aus dem von diesem Ausdruck erzeugten Typ abgeleitet. Ein Vorteil dieser Funktion besteht darin, dass in C++14 nur verschiebbare Variablen (z. B. std::unique_ptr) aus dem umgebenden Bereich erfasst und in Lambdas verwendet werden können.
pNums = make_unique<vector<int>>(nums); //... auto a = [ptr = move(pNums)]() { // use ptr };
Parameterliste
Neben der Erfassung von Variablen kann Lambda auch Eingabeparameter akzeptieren. Die Argumentliste (in der Standardsyntax Lambda-Deklarator genannt) ist optional und ähnelt in vielerlei Hinsicht der Argumentliste einer Funktion.
int y = [] (int first, int second) { return first + second; };
Wenn in C++14 der Parametertyp generisch ist, können Sie das Schlüsselwort auto als Typbezeichner verwenden. Dadurch wird der Compiler angewiesen, den Funktionsaufrufoperator als Vorlage zu erstellen. Jede Instanz von auto in der Parameterliste entspricht einem anderen Typparameter.
auto y = [] (auto first, auto second) { return first + second; };
lambda 表达式可以将另一个 lambda 表达式作为其参数。 有关详细信息,请参阅 Lambda 表达式的示例主题中的“高阶 Lambda 表达式”。
由于参数列表是可选的,因此在不将参数传递到 lambda 表达式,并且其 lambda-declarator: 不包含 exception-specification、trailing-return-type 或 mutable 的情况下,可以省略空括号。
可变规范
通常,lambda 的函数调用运算符为 const-by-value,但对 mutable 关键字的使用可将其取消。 它不会生成可变的数据成员。 利用可变规范,lambda 表达式的主体可以修改通过值捕获的变量。 本文后面的一些示例将显示如何使用 mutable。
异常规范
你可以使用 throw() 异常规范来指示 lambda 表达式不会引发任何异常。 与普通函数一样,如果 lambda 表达式声明 C4297 异常规范且 lambda 体引发异常,Visual C++ 编译器将生成警告 throw(),如下所示:
// throw_lambda_expression.cpp // compile with: /W4 /EHsc int main() // C4297 expected { []() throw() { throw 5; }(); }
返回类型
将自动推导 lambda 表达式的返回类型。 无需使用 auto 关键字,除非指定尾随返回类型。 trailing-return-type 类似于普通方法或函数的返回类型部分。 但是,返回类型必须跟在参数列表的后面,你必须在返回类型前面包含 trailing-return-type 关键字 ->。
如果 lambda 体仅包含一个返回语句或其表达式不返回值,则可以省略 lambda 表达式的返回类型部分。 如果 lambda 体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。 否则,编译器会将返回类型推导为 void。 下面的代码示例片段说明了这一原则。
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 is not valid
lambda 表达式可以生成另一个 lambda 表达式作为其返回值。 有关详细信息,请参阅 Lambda 表达式的示例中的“高阶 Lambda 表达式”。
Lambda 体
lambda 表达式的 lambda 体(标准语法中的 compound-statement)可包含普通方法或函数的主体可包含的任何内容。 普通函数和 lambda 表达式的主体均可访问以下变量类型:
从封闭范围捕获变量,如前所述。
参数
本地声明变量
类数据成员(在类内部声明并且捕获 this 时)
具有静态存储持续时间的任何变量(例如,全局变量)
以下示例包含通过值显式捕获变量 n 并通过引用隐式捕获变量 m 的 lambda 表达式:
// 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
由于变量 n 是通过值捕获的,因此在调用 lambda 表达式后,变量的值仍保持 0 不变。 mutable 规范允许在 lambda 中修改 n。
尽管 lambda 表达式只能捕获具有自动存储持续时间的变量,但你可以在 lambda 表达式的主体中使用具有静态存储持续时间的变量。 以下示例使用 generate 函数和 lambda 表达式为 vector 对象中的每个元素赋值。 lambda 表达式将修改静态变量以生成下一个元素的值。
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 is not thread-safe and is shown for illustration only }
下面的代码示例使用上一示例中的函数,并添加了使用 STL 算法 generate_n 的 lambda 表达式的示例。 该 lambda 表达式将 vector 对象的元素指派给前两个元素之和。 使用了 mutable 关键字,以使 lambda 表达式的主体可以修改 lambda 表达式通过值捕获的外部变量 x 和 y 的副本。 由于 lambda 表达式通过值捕获原始变量 x 和 y,因此它们的值在 lambda 执行后仍为 1。
// compile with: /W4 /EHsc #include#include #include #include using namespace std; template 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 is not 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 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
更多Kombiniert mit den neuen Funktionen von C++11, um die Verwendung von Lambda-Ausdrücken in C++ zu erlernen相关文章请关注PHP中文网!