
首先我们看一个例子:
#include <iostream>
using namespace std;
int main() {
int a = 0;
static int b = 0;
auto f = [=]() mutable {
cout << "in lambda f : " << ++a << ", " << ++b << endl;
};
f();
f();
cout << "in main : " << a << ", " << b << endl;
return 0;
}
输出结果为
in lambda f : 1, 1
in lambda f : 2, 2
in main : 0, 2
在第一次看见这个例子的时候,我预想到的a在f中的两次输出都应该为1,但真实的输出结果是在两次f的调用中,实现了累加,后来查阅资料发现:
lambda 表达式是纯右值表达式,它的类型是独有的无名非联合非聚合类类型,被称为闭包类型(closure type)
闭包类型::operator()(形参)
返回类型 operator()(形参) { 函数体 }
当被调用时,执行 lambda 表达式的函数体。当访问变量时,访问的是它被捕获的副本(对于以复制捕获的实体)或原对象(对于以引用捕获的实体)。除非 lambda 表达式中使用了关键词 mutable,否则函数调用运算符或运算符模板的 cv 限定符都会是 const,并且无法从这个 operator() 的内部修改以复制捕获的对象。
也就是说,对于lambda表达式,编译器会将其翻译成为一个类,该类中的重载operator()成员函数就是lambda函数本体。如果lambda表达式未使用mutable修饰,则operator()函数是const类型的,使用mutable可以解除该限制。
我们使用C++ Insights工具将上述代码转换成编译器角度的源代码,结果如下:
#include <iostream>
using namespace std;
int main()
{
int a = 0;
static int b = 0;
class __lambda_8_12
{
public:
inline /*constexpr */ void operator()()
{
std::operator<<(std::operator<<(std::cout, "in lambda f : ").operator<<(++a), ", ").operator<<(++b).operator<<(std::endl);
}
private:
int a;
public:
__lambda_8_12(int & _a)
: a{_a}
{}
};
__lambda_8_12 f = __lambda_8_12{a};
f.operator()();
f.operator()();
std::operator<<(std::operator<<(std::cout, "in main : ").operator<<(a), ", ").operator<<(b).operator<<(std::endl);
return 0;
}
注:C++ Insights 是从源码转换到更加详细的源码,把编译器看到的给展开。如
auto、template和decltype得到的类型、lambda、range-for循环的转换、潜藏的隐式类型转换等。
从展开结果可以看出,实际上编译器就是把lambda表达式转化成为一个类,lambda表达式捕获的值为该类的数据成员。上例中lambda表达式被转化为类__lambda_8_12,其重载了operator(),由于使用了mutable修饰,解除了operator()的const修饰(默认情况下是const的)。数据成员为捕获到的a,并将其实例化为类对象f,然后调用了两次operator(),因此a值的打印也是累加的,即两次结果分别为1和2。
总 结
lambda表达式实际上就是一个独有的无名非联合非聚合类,其捕获的数据是它的类成员,该类重载了operator(),且默认情况下该成员函数是const,可以使用mutable关键字来去除const限定。