首先我们看一个例子:
代码语言:javascript复制#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;
}
输出结果为
代码语言:javascript复制in lambda f : 1, 1
in lambda f : 2, 2
in main : 0, 2
在第一次看见这个例子的时候,我预想到的a
在f
中的两次输出都应该为1
,但真实的输出结果是在两次f
的调用中,实现了累加,后来查阅资料发现:
lambda 表达式是纯右值表达式,它的类型是独有的无名非联合非聚合类类型,被称为闭包类型(closure type)
代码语言:javascript复制闭包类型::operator()(形参)
返回类型 operator()(形参) { 函数体 }
当被调用时,执行 lambda 表达式的函数体。当访问变量时,访问的是它被捕获的副本(对于以复制捕获的实体)或原对象(对于以引用捕获的实体)。除非 lambda 表达式中使用了关键词 mutable,否则函数调用运算符或运算符模板的 cv 限定符都会是 const,并且无法从这个 operator() 的内部修改以复制捕获的对象。
也就是说,对于lambda
表达式,编译器会将其翻译成为一个类,该类中的重载operator()
成员函数就是lambda
函数本体。如果lambda
表达式未使用mutable
修饰,则operator()
函数是const
类型的,使用mutable
可以解除该限制。
我们使用C Insights工具将上述代码转换成编译器角度的源代码,结果如下:
代码语言:javascript复制#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
限定。