对lambda表达式用法进行总结,
参考:1. https://docs.microsoft.com/en-us/cpp/cpp/lambda-expression-syntax?view=vs-2019
2.《深入理解C++11》
lambda函数在C++11标准中默认是内联的,类似于其他语言中的局部函数(local function),或内嵌函数(nested function)。
lambda
lambda表达式与普通函数最大的区别之一是:lambda函数可以通过捕捉列表访问一些上下文中的数据,在捕捉列表中描述了哪些数据可以被lambda使用。
在C风格编程中,使用函数指针可以将一个函数可以传递给另外一个函数。函数指针不便于维护和理解。传递进来的函数可能被定义代码的其他位置。并且函数指针不是类型安全的。现代C++提供重载了()操作符的函数对象(function objects)/类,这就使得它们可以通过类似函数的形式被调用。创建函数对象最方便的方式是labmda表达式。
上述lambda表达式可以理解为:具有一个int类型的参数,且返回类型为bool,该函数用于判断变量的值是否大于x且小于y。注意:x y是定义在上下文中的,可以在lambda表达式中获取。[=]表明x y的值是通过值的形式捕获的(captured by value),也就是说在lambda表达式中x y是这些值的拷贝。
In C++11 and later, a lambda expression—often called a lambda—is a convenient way of defining an anonymous function object (a closure) right at the location where it is invoked or passed as an argument to a function. Typically lambdas are used to encapsulate a few lines of code that are passed to algorithms or asynchronous methods.lambda是一种在函数被调用处/传递变量给函数时定义匿名函数对象(闭包)的便利方式。典型地,lambda用于包裹少量行数的代码,用于传递给算法或匿名函数。
capture list
在labmda表达式的[]中,用于捕获外部变量,并指明是通过引用,还是通过值的形式捕获。在变量前如果有&符号则表明是通过引用捕获的;如果没有&则表明通过值捕获的(拷贝一份)。
注意:
在捕获列表中,= & this都最多只能出现一次。
- lambda表达式内部可以访问表达式外部的变量,capture list中的变量是在lambda表达式之前已经定义的变量,称为“捕获”了外部变量。
- [] 不捕获任何变量。
- [&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
- [=] 捕获外部作用域中所有变量,并作为副本copy在函数体中使用(按值捕获)。
- [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。
- [bar]按值捕获 bar 变量,同时不捕获其他变量。
- [this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。
注意,捕获列表有:引用,值两种方式,在变量值修改、多线程等情形下需要注意。
对于值传递,外部变量值改变不影响lamda内部的值,但是对于引用传递,外部变量值改变会影响lambda内部,调用labmda时需要注意。
如何捕获指针?:
使用捕获列表时需注意:
- 引用捕获可被用于修改外部变量,但是值捕获则不会,(mutable允许你将拷贝的副本进行修改,但是不会影响到外部的变量)
- 引用捕获会影响外部变量的更新,但是值捕获不会
- 引用捕获会引入生命周期依赖(lifetime dependency),但是值捕获则不会。这在lambda异步运行时尤为重要。如果你在异步运行的lambda表达式中通过引用捕获了一个外部变量,该变量很有可能在lambda未执行完就被销毁了,这就会导致访问冲突(access violation)。
Generalized capture(C++14)
在C++14中,你可以在lambda捕获列表中引入并初始化新变量,而无需在lambda函数的封闭范围{}内存在这些变量。变量的初始化可以使用任何表达式;新变量的类型由初始化表达式自动推导。
该特性的一个好处是,你可以从外部环境中捕获move-only变量(例如:unique_ptr),并且在lambda表达式中使用它。
parameter list参数列表
除捕获列表外,lambda表达式还可以输入参数。参数列表是可选的,且在多数情况下类似于函数列表。
在C++14中,如果参数是generic(泛型?),你可以使用auto关键字作为类型说明符。auto告诉编译器将函数调用运算符创建为模板(This tells the compiler to create the function call operator as a template)。而且labmda表达式可以以另外一个labmda表达式作为参数。
mutable specification
通过值传递进labmda表达式的变量是const-by-value,即在labmda内部不能修改这些变量,但是使用mutable修饰后,表达式内部就可以修改了。
exception指示符:表明lambda表达式是否会抛出异常,例如使用noexcept就表示该表达式不会抛出异常。
典型情况下,lambda的函数调用操作符(lambda’s function call operator)是const-by-value,但是通过使用mutable关键字可以解除该限定。该关键字并不会产生mutable数据成员。使用mutable关键字使得labmda表达式函数体可以修改捕获的外部变量的值。
Typically, a lambda's function call operator is const-by-value, but use of the mutable keyword cancels this out. It does not produce mutable data members. The mutable specification enables the body of a lambda expression to modify variables that are captured by value.
在C++11中,默认情况下lambda函数是一个const函数,按照规则,一个const的成员函数是不能在函数体中改变非静态成员变量的值得。如果在lambda表达式中有mutable,则不论是通过引用还是参数传递的方式捕获的外部变量,在lambda表达式内部都可以修改,且编译器不会报错。示例:
exception specification
你可以使用noexcept异常说明符来表明lambda表达式不会抛出任何异常。
返回类型
lambda表达式的返回值是自动推断的。你不必使用auto关键字,除非你指定了一个trailing-return-type。trailing-return-type指的是普通函数或方法的返回部分。在lambda表达式中,返回类型必须在参数列表后,且需要在返回类型前添加 -> trailing-return-type关键字。
如果lambda表达式只有一个return语句,或该表达式根本不返回值,你可以省略lambda表达式的返回部分。如果lambda函数体只有一个返回语句,编译器会根据返回表达式进行推断该表达式的返回类型。在其他情况下,编译器将返回类型推断为void。
当表达式中只有一个返回语句,或根本没有返回语句时,可以省略labmda表达式的返回类型。You can omit(忽略省略) the return-type part of a lambda expression if the lambda body contains just one return statement or the expression does not return a value.
返回类型格式:
-> type
例如:
lambda body
lambda表达式与普通函数体可包含的内容相同,可以使用如下变量:
- l 通过[]捕获的变量
- l 参数列表
- l 局部声明的变量
- l 如果捕获了this后,this的数据成员等
- l 静态变量,例如:全局变量
lambda使用
l 直接调用lambda
l 将lambda表达式作为变量传递给其他函数
lambda表达式嵌套
在lambda表达式中可以嵌套另外一个lambda表达式
函数与lambda表达式对比
当你编写代码时,你可能会使用函数指针和函数对象来解决问题和进行计算,特别是使用C++标准库算法(C++ Standard Library algorithms)时。函数指针和函数对象有其优点但是也有缺点:函数指针拥有最小的语法开销(syntactic overhead)但是在其范围内不能保留状态;函数对象可以维护状态但是需要在类中定义函数的语法开销。
lambda表达式结合了函数指针,函数对象的优点,并避免了它们的缺点。与函数对象类似,lambda比较灵活且可以维护状态(maintain state);但是与函数对象不同的是,它的紧凑语法不需要显式的类定义(explicit class definition)。
仿函数
在C++11之前,在使用STL算法时,通常会用到一种特殊的对象,称之为函数对象,或仿函数(functor)。仿函数简单地说,就是重定义了成员函数operator()的一种自定义类型对象。这样的对象在代码使用时与函数一样,但是其本质却并非函数。
class _functor { public: int operator()(int x, int y) { return x + y; } }; int main(int argc, char *argv[]) { _functor totalChild; return totalChild(5, 6);
}
相对于函数,仿函数可以拥有初始状态,一般通过class定义私有成员,并在声明对象的时候进行初始化。私有成员的状态就成了仿函数的初始状态。而由于声明一个仿函数对象可以拥有多个不同初始状态的实例,因此可以借仿函数产生多个功能类似却不同的仿函数实例。例如:
class Tax { private: float rate; int base; public: Tax(float r, int b) :rate(r), base(b) {} float operator()(float money){ return (money - base)*rate; } }; int main(int argc, char *argv[]) { //===functor Tax high(0.40, 30000); Tax middle(0.25, 20000); cout << "tax over 3w: " << high(37500) << endl; cout << "tax over 2w: " << middle(27500) << endl;
}
可以看出,带状态的仿函数与lambda类似,除去语法层面上的不同,lambda和仿函数有着相同的内涵-都可以捕捉一些变量作为初始状态,并接受参数进行运算。而事实上,仿函数是编译器实现lambda的一种方式,在实现阶段,通常编译器都会把lambda函数转化为一个仿函数对象。因此,在C++11中,lambda可以视为仿函数的一种等价形式了,或者是“语法糖”。
lambda书写简单,而且可以直接在STL算法中使用,可以部分替代仿函数。