lambda表达式是一种局部类类型,它含有一个构造函数,和一个const成员函数operator()()。
lambda表达式除了能做参数外,还能用于初始化一个声明为auto或者std::function
对于lambda表达式而言,有着很多种表述方法,并且有着多中优化途径。如果把lambda表达式看成是一种定义并使用函数对象的便捷方式,那么便于理解。
假设有这么一个例子:
void print_modulo(const vector& v, ostream& os, int m)
{
for_each(begin(v), end(v),
[&os, m](int x) {if (x % m == 0) os << x << '/n';}
);
}
我们可以将其中的lambda表达式改写成一个函数对象:
class Modulo_print
{
ostream& os;
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& v, ostream& os, int m)
{
for_each(begin(v), end(v),Modulo_print{os, m});
}
我们把由lambda表达式生成的函数对象称为闭包对象 (闭包)。
如果lambda表达式通过引用 ([&]) 捕获它定义环境中的每一个局部变量,则其闭包对象则可以优化为简单的包含一个指向外层栈框架的指针。
lambda表达式的主要用途是封装一部分代码以便将其用做参数。
有些lambda表达式无需访问它的局部环境,这样lambda表达式使用空引入符 [ ] 定义。
lambda引入符的形式有很多:
对于lambda表达式来说,当把lambda传递给另一个线程时,用值捕获更优,通过引用或者指针访问其他线程中的内容是一种危险操作,对于性能和正确性来说都是如此。
对于可变模板参数的捕获示例:
template
void algo(int s, Var... var)
{
auto helper = [&s, &var...]{return s * (h1(var...) + h2(var...)); };
}
lambda表达式的生命周期可能比它的被调用者更长。当我们把lambda表达式传递给另一个线程或者存在别处以供后续使用的时候。
例如:
void setup(Menu& m)
{
Point p1, p2, p3;
m.add("draw a triangle", [&]{m.draw(p1, p2, p3); }); //可能会发生程序错误
}
当setup完成之后,我们可能需要画一个三角型,此时lambda表达式将会访问一个已经不存在的局部变量。
如果我们发现一个lambda表达式的生命周期可能比它的调用者更长,则我们需要确保所有局部信息都已经被拷贝到闭包对象中去。
m.add("draw a triangle", [=]{m.draw(p1, p2, p3); });
当lambda表达式被用在成员函数当中,我们怎样去访问类成员变量呢?我们通过捕获this指针来访问类成员对象。
例如:
template
class Request
{
typedef typename vector::value_type result;
function&)> oper;
vector values;
result results;
public:
void execute()
{
[this] {results = oper(values);};
}
};
类成员变量是通过this访问的,在lambda表达式中[this] 和 [=] 互不兼容,因此一不小心就可能在多线程程序中造成竞争条件。
如果希望在lambda表达式修改函数对象的状态,即修改通过值捕获的变量,则可以使用mutable修饰符:
void algo()
{
int count = 100;
[count]()mutable {return --count;};
}
lambda表达式的大多数规则都是从类和函数借鉴过来的,然而需要有两点需要注意:
如果在一条lambda表达式的主体部分不包含return语句,则其返回类型为void。
如果lambda表达式只包含一条return语句,则返回类型是return表达式的类型。 其他情况,必须显式定义一个返回类型。
任意两个lambda表达式的类型都不相同,一旦两个lambda表达式的类型相同,则模板实例化机制就无法识别它们了。
例如:
auto algo = [&algo](char* b, char* a) {
swap(*b, *--a); algo(++b, a);
};
由于algo的类型并没有在使用之前被推断出来,因此上面的写法是错误的。
使用function包装器可以存入函数对象。就可以保证,在使用之前, algo的类型就已经知道了。
function algo = [&](char* b, char* a) {
swap(*b, *--a); algo(++b, a);
};
如果我们只是想给lambda表达式起个名字,而不会在主体内部递归的使用它。则:
auto algo = [&](char* b, char* a) {swap(*b, *--a); };
是没有问题的。
如果lambda表达式什么也不捕获,则我们可以把它赋值给一个指向正确类型的函数指针。
double (*fun) (double a) = [](double a) {return sqrt(a);};
但行好事, 莫问前程!