Home  >  Article  >  Backend Development  >  Combined with the new features of C++11 to learn the usage of lambda expressions in C++

Combined with the new features of C++11 to learn the usage of lambda expressions in C++

高洛峰
高洛峰Original
2017-01-23 13:57:091591browse

In C++11, a lambda expression (often called a "lambda") is a convenient way to define an anonymous function object at the point where it is called or passed as an argument to a function. Lambdas are typically used to encapsulate a small number of lines of code passed to an algorithm or asynchronous method. This article defines what lambdas are, compares lambdas to other programming techniques, describes their advantages, and provides a basic example.
Parts of a Lambda expression
The ISO C++ standard shows a simple lambda passed as the third parameter to the std::sort() function:

#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
 );
}

This diagram shows the components of a lambda:

Combined with the new features of C++11 to learn the usage of lambda expressions in C++

Capture clause (also called lambda bootstrapping in the C++ specification.)

Parameters List (optional). (Also known as lambda declarator)

Variable specification (optional).

Exception specification (optional).

Trailing return type (optional).

"lambda body"

Capture clause

Lambda can introduce new variables in its body (with C++14), it can also access (or " capture") variables in the surrounding scope. A lambda begins with a Capture clause (the lambda bootstrap in standard syntax), which specifies the variable to capture and whether to capture by value or reference. Variables prefixed with an ampersand (&) are accessed by reference, variables without this prefix are accessed by value.
The empty capture clause [ ] indicates that the body of the lambda expression does not access variables in the enclosing scope.
You can use the default capture mode (capture-default in the standard syntax) to indicate how to capture any external variables referenced in the lambda: [&] means capturing all variables referenced by reference, while [=] means capturing them by value . You can use the default capture mode and then explicitly specify the opposite mode for a specific variable. For example, if the lambda body accesses the external variable total by reference and the external variable factor by value, the following capture clause is equivalent:

[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]

Use capture-default , only the variables mentioned in the lambda will be captured.
If a capture clause contains capture-default&, then no capture in the identifier of that capture clause can take the form of & identifier. Likewise, if a capture clause contains capture-default=, the capture clause's capture cannot take the form = identifier. identifier or this cannot appear more than once in the capture clause. The following code snippets give some examples.

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 followed by an ellipsis is a package expansion, as shown in the following variadic template example:

template<class... Args>
void f(Args... args) {
 auto x = [args...] { return g(args...); };
 x();
}

To use a lambda expression in the body of a class method, pass the this pointer to the Capture clause to provide access to the enclosing class's methods and data members. For an example that shows how to use lambda expressions with class methods, see "Example: Using Lambda Expressions in Methods" in Examples for Lambda Expressions.
When using the capture clause, it is recommended that you remember the following points (especially when using lambdas that take multiple threads):
Reference capture can be used to modify external variables, but value capture cannot achieve this operation. (Mutable allows modification of the copy, but not the original item.)
Reference capture reflects updates to external variables, while value capture does not.
Reference capture introduces lifetime dependencies, while value capture has no lifetime dependencies. This is especially important when the lambda is run asynchronously. If you capture a local variable by reference in an async lambda, the local variable will most likely disappear when the lambda runs, causing a runtime access violation.

Universal capture (C++14)
In C++14, new variables can be introduced and initialized in the Capture clause without having these variables exist in the enclosing scope of the lambda function . Initialization can be expressed by any arbitrary expression; and the type of the new variable is deduced from the type produced by that expression. One benefit of this feature is that in C++14, move-only variables (such as std::unique_ptr) can be captured from the surrounding scope and used in lambdas.

pNums = make_unique<vector<int>>(nums);
//...
 auto a = [ptr = move(pNums)]()
 {
  // use ptr
 };

Parameter list
In addition to capturing variables, lambda can also accept input parameters. The argument list (called a lambda declarator in standard syntax) is optional and is in most ways similar to a function's argument list.

int y = [] (int first, int second)
{
 return first + second;
};

In C++14, if the parameter type is generic, you can use the auto keyword as a type specifier. This tells the compiler to create the function call operator as a template. Each instance of auto in the parameter list is equivalent to a different type parameter.

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

更多Combined with the new features of C++11 to learn the usage of lambda expressions in C++相关文章请关注PHP中文网!   


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn