C++避坑---lambda表达式变量的捕获与mutable关键字

2023-05-17 21:59:06 浏览数 (4)

首先我们看一个例子:

代码语言: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

在第一次看见这个例子的时候,我预想到的af中的两次输出都应该为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 是从源码转换到更加详细的源码,把编译器看到的给展开。如 autotemplatedecltype 得到的类型、lambdarange-for 循环的转换、潜藏的隐式类型转换等。

从展开结果可以看出,实际上编译器就是把lambda表达式转化成为一个类,lambda表达式捕获的值为该类的数据成员。上例中lambda表达式被转化为类__lambda_8_12,其重载了operator(),由于使用了mutable修饰,解除了operator()const修饰(默认情况下是const的)。数据成员为捕获到的a,并将其实例化为类对象f,然后调用了两次operator(),因此a值的打印也是累加的,即两次结果分别为12

总 结

lambda表达式实际上就是一个独有的无名非联合非聚合类,其捕获的数据是它的类成员,该类重载了operator(),且默认情况下该成员函数是const,可以使用mutable关键字来去除const限定。

1 人点赞