C++-Lambda实现原理

有关Lambda语法

参见此处

实现原理

1
2
3
4
5
6
7
8
9
10
void LambdaTest()
{
int a = 1;
int b = 3;
auto lambda = [a, &b](int x, int y)mutable throw() -> bool
{
return a * b > x * y;
};
bool ret = lambda(3, 4);
}

编译器实现时大概分为如下步骤

  1. 创建对于的 lambda 类,捕获的值为类内成员变量,重载 operator();
  2. 创建 lambda 对象;
  3. 通过对象调用 operator()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class lambda_xxxx
{
private:
int a;
int& b;
public:
lambda_xxxx(int _a, int& _b) : a(_a), b(_b) {}
bool operator()(int x, int y) throw()
{
return a * b > x * y;
}
};
void LambdaTest()
{
int a = 1;
int b = 3;
lambda_xxxx lambda = lambda_xxxx(a, b);
bool ret = lambda(3, 4);
}

解析

  1. lambda 表达式捕获列表对应类中 private 成员;
  2. lambda 表达式形参对应类中 operator() 的形参
  3. lambda 表达式中mutable关键词对应 operator() 的 const 修饰
  4. lambda 表达式中的返回类型对应 operator() 的返回类型
  5. lambda 表达式中函数体对应类中 operator() 的函数体
  6. 且当 lambda 表达式捕获方式不同,也会影响类中 private 成员的类型:
    1. 当使用**值捕获(=)**,private 成员的类型与捕获变量类型一致;
    2. 当使用**引用捕获(&)**,private 成员的类型也是捕获变量的引用类型。
  7. 类名 lambda_xxxx 的 xxxx 是为了防止命名冲突加上的。

特殊情况

转自链滴

当 lambda 表达式不捕获任何外部变量时,特定情况下会有额外的代码产生。

其中,特定情况是指:有 lambda_xxxx 类 到 函数指针 的类型转换
如以下代码

1
2
3
4
5
6
7
8
9
10
11
typedef int(_stdcall *Func)(int);
int Test(Func func)
{
return func(1);
}
void LambdaDemo()
{
Test([](int i) {
return i;
});
}
  1. Test 函数接受一个函数指针作为参数,并调用这个函数指针。
  2. 实际调用 Test 时,传入的参数却是一个 Lambda 表达式,所以这里有一个类型的隐式转换
    lambda_xxxx -> 函数指针。

上面已经提到,Lambda 表达式就是一个 lambda_xxxx 类的匿名对象,与函数指针之间按理说不应该存在转换,但是上述代码却没有问题。

其问题关键在于,上述代码中,lambda 表达式没有捕获任何外部变量,即 lambda_xxxx 类没有任何成员变量,在 operator() 中也就不会用到任何成员变量,也就是说,operator() 虽然是个成员函数,它却不依赖 this 就可以调用。

因为不依赖 this,所以 一个 lambda_xxxx 类的匿名对象与函数指针之间就存在转换的可能。

大致过程如下:

  1. 在 lambda_xxxx 类中生成一个静态函数,静态函数的函数签名与 operator() 一致,**在这个静态函数中,通过一个空指针去调用该类的 operator()**;
  2. 在 lambda_xxxx 重载与函数指针的类型转换操作符,在这个函数中,返回第 1 步中静态函数的地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef int(_stdcall *Func)(int);

class lambda_xxxx
{
private:
// 没有捕获任何外部变量,所以没有成员
public:
int operator()(int i)
{
return i;
}
static int _stdcall lambda_invoker_stdcall(int i)
{
return ((lambda_xxxx *)nullptr)->operator()(i);
}
operator Func() const
{
return &lambda_invoker_stdcall;
}
};

int Test(Func func)
{
return func(1);
}
void LambdaDemo()
{
auto lambda = lambda_xxxx ();
Func func = lambda.operator Func();
Test(func);
}

上述代码只是以 __stdcall 调用约定的函数指针举例,实际使用时,对于不同调用约定,会生成对应版本的静态函数和类型转换函数。