C++ lambda表达式

94 阅读6分钟

lambda表达式

lambda表达式概述

Lambda 表达式在表达能力上和仿函数是等价的。编译器一般也是通过自动生成类似仿函数的代码来实现 Lambda 表达式。一个完整的 Lambda 表达式的组成如下:

[ capture-list ] ( params ) mutable(optional) exception(optional) attribute(optional) -> ret(optional) { body } 

除了capture-list,Lambda 表达式的其它地方其实和普通的函数基本一样。Lambda表达式最基本的两种参数捕获方式为按值捕获(Capture by Value)和按引用捕获(Capture by Reference)。按值捕获只可以获取调用变量的值,但是为lambda函数加上mutable关键字后被捕获的值在lambda函数作用域内可以进行值的修改操作,离开作用域后就会变回原值;按引用捕获获取变量的引用,可以在作用域内对变量进行任意修改。而对于超出lambda函数作用范围/生存期的变量,如全局变量,静态变量,动态分配(new)的变量等,无须被Lambda表达式显示捕获便可以被随意修改,相当于按引用捕获的状态。下面举具体代码例子:

#include <iostream>

void print_reset(int &a, int &b, int &c, int &d) {
  std::cout << "m:" << a << ", n:" << b 
    << ", o:" << c << ", p:" << d << std::endl;
  a = 0;
  b = 1;
  c = 2;
  d = 3;
}

int p = 3;

int main()
{
  int m = 0, n = 1, o = 2;

  [&] (int arg)  { return m = ++n + arg + ++o + ++p; }(4);
  print_reset(m, n, o, p);

  [&m] (int arg)  { return ++m + ++p ; }(4);
  print_reset(m, n, o, p);

  auto test = [&, n, o] (int arg) mutable { return m = n + arg + ++o + ++p; };
  test(4);
  print_reset(m, n, o, p);

  [&, n] (int arg) { return m = n + arg + ++o + ++p; }(4);
  print_reset(m, n, o, p);

  [=] (int arg) mutable { return m = ++n + arg + ++o + ++p; }(4);
  print_reset(m, n, o, p);

  [m, n, o] (int arg) mutable { return m = ++n + arg + ++o + ++p; }(4);
  print_reset(m, n, o, p);

  [] (int arg) { std::cout << "arg is: " << arg << ", p:" << p << std::endl; }(4);
}
  • 以上代码输出为:
m:13, n:2, o:3, p:4  
m:1, n:1, o:2, p:4  
m:12, n:1, o:2, p:4  
m:12, n:1, o:3, p:4  
m:0, n:1, o:2, p:4  
m:0, n:1, o:2, p:4  
arg is: 4, p:3

下面对每一个lambda表达式作具体解释。

  • [&] (int arg) { return m = ++n + arg + ++o + ++p; }(4);:按引用捕获当前作用域的所有变量。实现原理为编译器看到[&]后找到AST中当前作用域下的所有局部变量,然后构造一个匿名结构体并记录它们的引用。
  • [&m] (int arg) { return ++m + ++p ; }(4);:只按引用捕获变量m,全局变量p无须显示捕获。
  • auto test = [&, n, o] (int arg) mutable { return m = n + arg + ++o + ++p; }; test(4);:声明lambda函数,只有通过test(4)显示调用才能调用此函数。[&, n, o]表示按引用捕获除n, o外的所有变量,此时n, o退化为按值捕获,mutable关键字使得在表达式内可以对按值引用变量o进行++o的操作,但是一旦离开表达式,o便会变回原值。下面是编译器处理后的结果,可以看到其在类中也实现为operator()与仿函数等价
class __lambda_24_15
  {
    public: 
    inline /*constexpr */ int operator()(int arg)
    {
      return m = (((n + arg) + ++o) + ++p);
    }
    
    private: 
    int n;
    int o;
    int & m;
    
    public:
    __lambda_24_15(int & _n, int & _o, int & _m)
    : n{_n}
    , o{_o}
    , m{_m}
    {}
  };
  
  __lambda_24_15 test = __lambda_24_15{n, o, m};
  test.operator()(4);
  • [&, n] (int arg) { return m = n + arg + ++o + ++p; }(4);:按引用捕获除n外的所有变量。此时对o进行操作便不需要mutable关键字。
  • [=] (int arg) mutable { return m = ++n + arg + ++o + ++p; }(4);:按值捕获所有变量,在lamble表达式内被捕获的变量可以被操作,离开表达式后恢复原值。
  • [m, n, o] (int arg) mutable { return m = ++n + arg + ++o + ++p; }(4);:按值捕获m, n, o
  • [] (int arg) { std::cout << "arg is: " << arg << ", p:" << p << std::endl; }(4);:不捕获任何变量,但是全局变量可被引用。

嵌套lambda表达式

查看以下代码:

#include <iostream>
int main()
{
  const __int32_t ram_num = 0x12345678;
  auto emit_func = [&](int first, auto func) {
    func(ram_num);
  };
  auto emit_float = [&](float first, auto func) {
    func(first);
  };
  auto emit_int = [&](int first, auto func) {
    func(first);
  };
  int test1 = 2, test2 = 2, test3 = 2;
  float test_f = 5.01;
  emit_func(test1, [&](__int32_t input_num) { std::cout << "test1: " << test1 << " input: " << ++input_num << "\n"; });
  emit_func(test2, [=](__int32_t input_num) mutable { std::cout << "test2: " << ++test2 << " input: " << input_num << "\n"; });
  emit_int(test_f, [=](float input_testf) { std::cout << "testi: " << test_f << " input: " << input_testf << "\n"; });
  emit_float(test_f, [=](float input_testf) { std::cout << "testf: " << test_f << " input: " << input_testf << "\n"; });
}

输出如下:

test1: 2 input: 305419897  
test2: 3 input: 305419896  
testi: 5.01 input: 5  
testf: 5.01 input: 5.01
  • 对于嵌套lambda表达式,编译器在处理表达式声明时就会寻找作用域内所有调用该表达式的函数,并在lambda表达式基类中为其进行构造类声明,如对emit_func函数来说,其被调用两次,则为其构造两个模板类。构造类由cppinsight生成。
auto emit_func = [&](int first, auto func) {
    func(ram_num);
};
emit_func(test1, [&](__int32_t input_num) { std::cout << "test1: " << test1 << " input: " << ++input_num << "\n"; });
emit_func(test2, [=](__int32_t input_num) mutable { std::cout << "test2: " << ++test2 << " input: " << input_num << "\n"; });
// 编译器构造类
class __lambda_6_20
  {
    public: 
    template<class type_parameter_0_0>
    inline /*constexpr */ auto operator()(int first, type_parameter_0_0 func) const
    { func(ram_num); }
    
    template<> // 特化版本的templete
    inline /*constexpr */ void operator()<__lambda_17_20>(int first, __lambda_17_20 func) const
    { func.operator()(ram_num); }
    
    template<>
    inline /*constexpr */ void operator()<__lambda_18_20>(int first, __lambda_18_20 func) const
    { func.operator()(ram_num); }
    
    private: 
    const __int32_t & ram_num;
    
    public:
    __lambda_6_20(const int & _ram_num)
    : ram_num{_ram_num}
    {}
};
__lambda_6_20 emit_func = __lambda_6_20{ram_num};
  • 子类具体实现:
  class __lambda_17_20
  {
    public: 
    inline /*constexpr */ void operator()(__int32_t input_num) const
    {
      std::operator<<(std::operator<<(std::operator<<(std::cout, "test1: ").operator<<(test1), " input: ").operator<<(++input_num), "\n");
    }
    
    private: 
    int & test1;
    
    public:
    __lambda_17_20(int & _test1)
    : test1{_test1}
    {}
    
  } __lambda_17_20{test1};
  
  emit_func.operator()(test1, __lambda_17_20);

  class __lambda_18_20
  {
    public: 
    inline /*constexpr */ void operator()(__int32_t input_num)
    {
      std::operator<<(std::operator<<(std::operator<<(std::cout, "test2: ").operator<<(++test2), " input: ").operator<<(input_num), "\n");
    }
    
    private: 
    int test2;
    
    public:
    __lambda_18_20(int & _test2)
    : test2{_test2}
    {}
    
  } __lambda_18_20{test2};
  
  emit_func.operator()(test2, __lambda_18_20);
  • 而对于父函数和子函数传入参数不同类型的情况(不考虑auto),编译器会根据父类所定义的传入参数的类型来确定最终输出的参数类型。
emit_int(test_f, [=](float input_testf) { std::cout << "testi: " << test_f << " input: " << input_testf << "\n"; });
// 父类函数
  class __lambda_12_19
  {
    public: 
    template<class type_parameter_0_0>
    inline /*constexpr */ auto operator()(int first, type_parameter_0_0 func) const
    {
      func(first);
    }
    
    template<>
    inline /*constexpr */ void operator()<__lambda_19_20>(int first, __lambda_19_20 func) const
    {
      func.operator()(first);
    }
  };
  
  __lambda_12_19 emit_int = __lambda_12_19{};
// 子类函数
class __lambda_19_20
  {
    public: 
    inline /*constexpr */ void operator()(float input_testf) const
    {
      std::operator<<(std::operator<<(std::operator<<(std::cout, "testi: ").operator<<(test_f), " input: ").operator<<(input_testf), "\n");
    }
    
    private: 
    float test_f;
    
    public:
    __lambda_19_20(float & _test_f)
    : test_f{_test_f}
    {}
    
  } __lambda_19_20{test_f};
  
emit_int.operator()(static_cast<int>(test_f), __lambda_19_20); // 根据父类定义自动转换传入参数类型