什么是C ++ 11中的lambdaexpression式?

什么是C ++ 11中的lambdaexpression式? 我什么时候可以用一个? 他们解决什么types的问题在引入之前是不可能的?

一些例子和用例会很有用。

问题

C ++包括有用的通用函数,如std::for_eachstd::transform ,这可以非常方便。 不幸的是,它们的使用也很麻烦,特别是如果你想要应用的函数对于特定函数是独一无二的。

 #include <algorithm> #include <vector> namespace { struct f { void operator()(int) { // do something } }; } void func(std::vector<int>& v) { ff; std::for_each(v.begin(), v.end(), f); } 

如果你只用了一次,并且在那个特定的地方,写一整堂课只是为了做一件小事,一件事而已。

在C ++ 03中,你可能会试图写下如下的东西,以保持函子本地:

 void func2(std::vector<int>& v) { struct { void operator()(int) { // do something } } f; std::for_each(v.begin(), v.end(), f); } 

然而这是不允许的, f不能被传递给C ++ 03中的模板函数。

新的解决scheme

C ++ 11引入lambda允许你编写一个内联的匿名仿函数来代替struct f 。 对于简单的小例子来说,它可以更清晰地阅读(它将所有内容保存在一个地方),并且可能更简单,例如以最简单的forms:

 void func3(std::vector<int>& v) { std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ }); } 

Lambda函数只是匿名函数的语法糖。

返回types

在简单情况下,lambda的返回types是为你推导的,例如:

 void func4(std::vector<double>& v) { std::transform(v.begin(), v.end(), v.begin(), [](double d) { return d < 0.00001 ? 0 : d; } ); } 

但是当你开始编写更复杂的lambda时,你会很快遇到编译器无法推导出返回types的情况,例如:

 void func4(std::vector<double>& v) { std::transform(v.begin(), v.end(), v.begin(), [](double d) { if (d < 0.0001) { return 0; } else { return d; } }); } 

要解决这个问题,你可以使用-> T来为lambda函数明确指定一个返回types:

 void func4(std::vector<double>& v) { std::transform(v.begin(), v.end(), v.begin(), [](double d) -> double { if (d < 0.0001) { return 0; } else { return d; } }); } 

“捕捉”variables

到目前为止,我们还没有使用除传递给它内部lambda以外的任何内容,但是我们也可以在lambda内使用其他variables。 如果你想访问其他的variables,你可以使用capture子句(expression式的[] ),这个例子中迄今还没有被使用,例如:

 void func5(std::vector<double>& v, const double& epsilon) { std::transform(v.begin(), v.end(), v.begin(), [epsilon](double d) -> double { if (d < epsilon) { return 0; } else { return d; } }); } 

您可以通过引用和值来捕获,您可以分别使用&=指定值:

  • 通过引用俘获
  • [&]通过引用捕获lambda中使用的所有variables
  • [=]通过值捕获lambda中使用的所有variables
  • [&, epsilon]用[& [&, epsilon]捕获variables,但是按值赋值为epsilon
  • [=, &epsilon]捕获像[=]的variables,但通过引用的epsilon

生成的operator()在默认情况下是const ,默认情况下,当你访问它们时,捕获的含义是const 。 这样做的结果是每个具有相同input的调用都会产生相同的结果,但是您可以将lambda标记为mutable来请求生成的operator()不是const

什么是lambda函数?

lambda函数的C ++概念起源于lambda演算和函数式编程。 lambda是一个未命名的函数,在实际的编程中非常有用,但对于不可重复使用的短代码片段并不值得命名。

在C ++中,lambda函数是这样定义的

 []() { } // barebone lambda 

或者所有的荣耀

 []() mutable -> T { } // T is the return type, still lacking throw() 

[]是捕获列表, ()参数列表和{}函数体。

捕获列表

捕获列表定义了函数体内lambda的外部应该是什么,以及如何。 它可以是:

  1. 值:[x]
  2. 一个参考[​​&x]
  3. 目前在参考范围内的任何variables[&]
  4. 与3相同,但值[=]

你可以用逗号分隔的列表[x, &y]混合上述任何一个。

参数列表

参数列表与其他C ++函数中的相同。

function体

实际调用lambda时将执行的代码。

返回types扣除

如果lambda只有一个return语句,则可以省略返回types,并且具有隐式types的decltype(return_statement)

易变的

如果一个lambda标记为mutable(例如[]() mutable { } ),则允许mutate通过value捕获的值。

用例

由ISO标准定义的库很大程度上受益于lambdaexpression式,并且提高了可用性的几个方面,因为现在用户不需要在一些可访问的范围内使用小的函数来混淆他们的代码。

C ++ 14

在C ++中,14个lambda已经被各种提议所扩展。

初始化的Lambda捕获

捕获列表的一个元素现在可以用=来初始化。 这允许重命名variables并通过移动来捕获。 以标准为例:

 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. 

和一个从维基百科显示如何捕捉与std::move

 auto ptr = std::make_unique<int>(10); // See below for std::make_unique auto lambda = [ptr = std::move(ptr)] {return *ptr;}; 

通用Lambdas

Lambdas现在可以是generics的(如果T是周围范围中的某个types模板参数, auto在这里等于T ):

 auto lambda = [](auto x, auto y) {return x + y;}; 

改进的退货types扣除

C ++ 14允许为每个函数推导返回types,并且不会将其限制为formsreturn expression;函数return expression; 。 这也延伸到lambda。

Lambdaexpression式通常用于封装algorithm,以便将它们传递给另一个函数。 但是, 定义时可以立即执行一个lambdaexpression式

 [&](){ ...your code... }(); // immediately executed lambda expression 

在function上等同于

 { ...your code... } // simple code block 

这使得lambdaexpression式成为重构复杂函数的有力工具 。 如上所示,首先将一个代码段封装在一个lambda函数中。 显式参数化的过程可以在每个步骤之后通过中间testing逐渐执行。 一旦完成了代码块的参数化(如删除& ),您可以将代码移到外部位置并使其成为正常function。

同样,你可以使用lambdaexpression式来初始化基于algorithm结果的variables

 int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5! 

作为划分程序逻辑的一种方法 ,你甚至可能会发现将lambdaexpression式作为parameter passing给另一个lambdaexpression式是有用的。

 [&]( std::function<void()> algorithm ) // wrapper section { ...your wrapper code... algorithm(); ...your wrapper code... } ([&]() // algorithm section { ...your algorithm code... }); 

Lambdaexpression式还允许您创build命名的嵌套函数 ,这可以是避免重复逻辑的一种便捷方式。 在将非平凡函数作为parameter passing给另一个函数时,使用命名lambdas也往往会更容易在眼睛上(与匿名内联lambda相比)。 注意:不要忘记在大括号之后的分号。

 auto algorithm = [&]( double x, double m, double b ) -> double { return m*x+b; }; int a=algorithm(1,2,3), b=algorithm(4,5,6); 

如果后续分析揭示了函数对象的显着初始化开销,则可以select将其重写为普通函数。

答案

问:什么是C ++ 11中的lambdaexpression式?

答:在引擎盖下,它是重载operator()const的自动生成类的对象。 这个对象被称为闭包 ,由编译器创build。 这个'闭包'的概念已经接近C ++ 11的绑定概念了。 但是lambda通常会生成更好的代码。 并通过closures呼叫允许完全内联。

问:我什么时候可以用一个?

答:要定义“简单和小逻辑”,并要求编译器从前一个问题执行代。 你给一个编译器一些你想在operator()里面的expression式。 所有其他的东西编译器会生成给你。

问:在他们推出之前,他们解决了哪一类问题是不可能的?

答:这是一种类似于运算符重载的语法糖,而不是自定义添加,子操作的函数…但是它可以节省更多的不必要的代码行来将1-3行的真实逻辑包装到某些类中,等等。 有些工程师认为,如果线数越小,出错的几率就越小(我也这么认为)

使用示例

 auto x = [=](int arg1){printf("%i", arg1); }; void(*f)(int) = x; f(1); x(1); 

有关lambda的其他信息,不包括在问题中。 如果您不感兴趣,请忽略此部分

1.捕获的值。 你可以捕捉什么

1.1。 你可以在lamdas中引用具有静态存储持续时间的variables。 他们都被抓获。

1.2。 您可以使用lamda获取“按值”的捕获值。 在这种情况下,捕获的variables将被复制到函数对象(闭包)中。

 [captureVar1,captureVar2](int arg1){} 

1.3。 你可以捕捉参考。 & – 在这方面是指参考,而不是指针。

  [&captureVar1,&captureVar2](int arg1){} 

1.4。 通过价值或参考来捕获所有非静态variables是存在的表示法

  [=](int arg1){} // capture all not-static vars by value [&](int arg1){} // capture all not-static vars by reference 

1.5。 通过值来捕获所有非静态variables,或者通过引用和指定smth,这是存在的表示法。 更多。 示例:按值捕获所有非静态variables,但通过引用捕获参数2

 [=,&Param2](int arg1){} 

通过引用捕获所有非静态variables,但是通过值捕获参数2

 [&,Param2](int arg1){} 

2.返回types扣除

2.1。 如果lamda是一个expression式,则可以推导出Lambda返回types。 或者你可以明确地指定它。

 [=](int arg1)->trailing_return_type{return trailing_return_type();} 

如果lambda具有多于一个expression式,则返回types必须通过结尾返回types指定。 同样的语法也可以应用于自动函数和成员函数

3.捕获的值。 你不能捕捉什么

3.1。 您只能捕获本地variables,而不是对象的成员variables。

4.Сonversions

4.1。 lambda不是函数指针,它不是一个匿名函数 ,但可以隐式转换为函数指针。

PS

  1. 关于lambda语法信息的更多信息可以在编程语言C ++#337,2012-01-16,5.1.2的Working draft中find。 Lambda Expressions,第88页

  2. 在C ++ 14中,添加了名为“init capture”的额外function。 它允许执行closures数据成员的任意声明:

     auto toFloat = [](int value) { return float(value);}; auto interpolate = [min = toFloat(0), max = toFloat(255)](int value)->float { return (value - min) / (max - min);}; 

一个lambda函数是一个匿名函数,可以在线创build。 它可以捕获一些已经解释的variables(例如http://www.stroustrup.com/C++11FAQ.html#lambda ),但是有一些限制。 例如,如果有这样的callback接口,

 void apply(void (*f)(int)) { f(10); f(20); f(30); } 

你可以在现场编写一个函数来使用它,就像下面通过的函数一样:

 int col=0; void output() { apply([](int data) { cout << data << ((++col % 10) ? ' ' : '\n'); }); } 

但是你不能这样做:

 void output(int n) { int col=0; apply([&col,n](int data) { cout << data << ((++col % 10) ? ' ' : '\n'); }); } 

由于C ++ 11标准的限制。 如果你想使用捕获,你必须依靠图书馆和

 #include <functional> 

(或者其他一些类似algorithm的STL库来间接获取它),然后使用std :: function而不是像这样的parameter passing正常的函数:

 #include <functional> void apply(std::function<void(int)> f) { f(10); f(20); f(30); } void output(int width) { int col; apply([width,&col](int data) { cout << data << ((++col % width) ? ' ' : '\n'); }); } 

lambda expression的最佳解释之一是由C ++ Bjarne Stroustrup的作者在他的着作“ ***The C++ Programming Language***第11章( ISBN-13:978-0321563842 )中给出的:

What is a lambda expression?

一个lambdaexpression式 ,有时也被称为lambda函数,或者(严格来说不正确,但俗称)作为lambdaexpression式 ,是一个定义和使用匿名函数对象的简化符号。 用一个运算符()来定义一个命名类,而不是用一个运算符()来定义一个命名类,然后再创build这个类的一个对象,最后调用它,我们可以使用简写。

When would I use one?

当我们想把一个操作作为一个parameter passing给一个algorithm时,这是特别有用的。 在graphics用户界面(和其他地方)的情况下,这样的操作通常被称为callback

What class of problem do they solve that wasn't possible prior to their introduction?

在这里我想,用lambdaexpression式完成的每一个操作都可以在没有它们的情况下解决,但是代码更多,复杂度更高。 Lambdaexpression式这是您的代码优化的方式,并使其更具吸引力。 Stroustup感到难过:

有效的优化方法

Some examples

通过lambdaexpression式

 void print_modulo(const vector<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'; }); } 

或通过function

 class Modulo_print { ostream& os; // members to hold the capture list int m; public: Modulo_print(ostream& s, int mm) :os(s), m(mm) {} void operator()(int x) const { if (x%m==0) os << x << '\n'; } }; 

甚至

 void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0 { class Modulo_print { ostream& os; // members to hold the capture list int m; public: Modulo_print (ostream& s, int mm) :os(s), m(mm) {} void operator()(int x) const { if (x%m==0) os << x << '\n'; } }; for_each(begin(v),end(v),Modulo_print{os,m}); } 

如果你需要你可以像下面这样命名lambda expression

 void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0 { auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; }; for_each(begin(v),end(v),Modulo_print); } 

或者假设另一个简单的样本

 void TestFunctions::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); } ); } 

将生成下一个

0

1

0

1

0

1

0

1

0

1

0sortingx -1; x-3; x-4; x-5; x-6; x-7; x-33;

[] – 这是捕获列表或lambda introducer :如果lambdas不需要访问他们的本地环境,我们可以使用它。

从书中引用:

lambdaexpression式的第一个字符总是[ 。 lambda介绍人可以采取各种forms:

[] :空的捕获列表。 这意味着在lambda体中不能使用周围环境的本地名称。 对于这样的lambdaexpression式,数据是从参数或非本地variables中获得的。

[&] :通过引用隐式捕获。 所有本地名称都可以使用。 所有本地variables都通过引用来访问。

[=] :按值隐式捕获。 所有本地名称都可以使用。 所有名称都是指在lambdaexpression式的调用点处获取的局部variables的副本。

[捕获列表]:显式捕获; 捕获列表是通过引用或通过值来捕获(即,存储在对象中)的局部variables的名称的列表。 名称前面带有&的variables被引用捕获。 其他variables被值捕获。 一个捕获列表也可以包含这个和名字后跟…作为元素。

[&,capture-list] :通过引用隐式地捕获列表中没有提及名字的所有局部variables。 捕获列表可以包含这个。 列出的名字不能以&开头。 捕获列表中指定的variables是按值捕获的。

[=,capture-list] :通过值隐式地捕获列表中未提及名称的所有局部variables。 捕获列表不能包含这个。 列出的名称必须以“&”开头。 在捕获列表中命名的variables是通过引用捕获的。

请注意,以&开头的本地名称始终由引用捕获,并且本地名称不会由&始终由值捕获。 只有通过引用捕获才能修改调用环境中的variables。

Additional

Lambda expression格式

在这里输入图像描述

其他参考:

  • 维基
  • open-std.org ,章节5.1.2

它解决的一个问题是: 在构造函数中使用输出参数函数初始化一个const成员的调用比lambda更简单

你可以通过调用一个函数来初始化你的类的一个const成员,通过返回它的输出作为输出参数来设置它的值。

那么,我发现的一个实际用途是减less锅炉板代码。 例如:

 void process_z_vec(vector<int>& vec) { auto print_2d = [](const vector<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); //... } 

没有lambda,你可能需要为不同的情况做些事情。 当然你可以创build一个函数,但是如果你想限制灵魂用户函数范围内的用法呢? 拉姆达的性质符合这个要求,我使用它的情况下。