C++ Lambda 原理和编译器实现

1.Lambda 表达式语法

Lambda 表达式完整的格式如下:

[捕获列表] (形参列表) mutable 异常列表-> 返回类型
{
    函数体
}

各项的含义:

  1. 捕获列表:捕获外部变量,捕获的变量可以在函数体中使用,可以省略,即不捕获外部变量。
  2. 形参列表:和普通函数的形参列表一样。可省略,即无参数列表
  3. mutable:mutable 关键字,如果有,则表示在函数体中可以修改捕获变量,根据具体需求决定是否需要省略。
  4. 异常列表:noexcept / throw(...),和普通函数的异常列表一样,可省略,即代表可能抛出任何类型的异常。
  5. 返回类型:和函数的返回类型一样。可省略,如省略,编译器将自动推导返回类型。
  6. 函数体:代码实现。可省略,但是没意义。

使用示例

void LambdaDemo()
{
    int a = 1;
    int b = 2;
    auto lambda = [a, b](int x, int y)mutable throw() -> bool
    {
        return a + b > x + y;
    };
    bool ret = lambda(3, 4);
}

编译器实现原理

编译器实现 lambda 表达式大致分为一下几个步骤

  1. 创建 lambda 类,实现构造函数,使用 lambda 表达式的函数体重载 operator()(所以 lambda 表达式 也叫匿名函数对象)
  2. 创建 lambda 对象
  3. 通过对象调用 operator()

编译器将 lambda 表达式翻译后的代码:

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 LambdaDemo()
{
    int a = 1;
    int b = 2;
    lambda_xxxx lambda = lambda_xxxx(a, b);
    bool ret = lambda.operator()(3, 4);
}

其中,类名 lambda_xxxx 的 xxxx 是为了防止命名冲突加上的。

lambda_xxxx 与 lambda 表达式 的对应关系

  1. lambda 表达式中的捕获列表,对应 lambda_xxxx 类的 private 成员
  2. lambda 表达式中的形参列表,对应 lambda_xxxx 类成员函数 operator() 的形参列表
  3. lambda 表达式中的 mutable,对应 lambda_xxxx 类成员函数 operator() 的常属性 const,即是否是 常成员函数
  4. lambda 表达式中的返回类型,对应 lambda_xxxx 类成员函数 operator() 的返回类型
  5. lambda 表达式中的函数体,对应 lambda_xxxx 类成员函数 operator() 的函数体

 

另外,lambda 表达 捕获列表的捕获方式,也影响 对应 lambda_xxxx 类的 private 成员 的类型

  1. 值捕获:private 成员 的类型与捕获变量的类型一致
  2. 引用捕获:private 成员 的类型是捕获变量的引用类型

不捕获任何外部变量

如果 lambda 表达式不捕获任何外部变量,在特定的情况下,会有额外的代码生成。
其中,特定情况是指:有 lambda_xxxx 类函数指针 的类型转换
如以下代码

typedef int(_stdcall *Func)(int);
int Test(Func func)
{
	return func(1);
}
void LambdaDemo()
{
	Test([](int i) {
		return i;
	});
}

Test 函数接受一个函数指针作为参数,并调用这个函数指针。

实际调用 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 步中静态函数的地址。

上述代码在编译器的翻译后代码如下:

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 调用约定的函数指针举例,实际使用时,对于不同调用约定,会生成对应版本的静态函数和类型转换函数

以上结论的正确性可以通过显式调用 lambda 的转换函数与反汇编来证明

void LambdaDemo()
{
	auto lambda = [](int i) {return i;};
	Func func = lambda.operator Func();
	Test(func);
}
  • 转为函数指针

  • 将静态函数 lambda_invoker_stdcall 地址作为 类型转换函数的返回值

  • 静态函数 lambda_invoker_stdcall 中,使用 0 作为 this 调用 operator()

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Lambda函数是一种匿名函数,它可以在需要函数参数的地方代替完整的函数定义。Lambda函数在C++11标准中引入,在C中也有类似的概念。 Lambda函数的基本语法是:[捕获列表](参数列表) -> 返回类型 { 函数体 } 捕获列表是用来捕获外部变量的方式。在Lambda函数中,可以捕获外部的自动变量和静态变量。捕获可以按值或按引用进行,通过使用"="或"&"来指定。 Lambda函数的参数列表是用来指定函数参数的类型和名称。 返回类型是用来指定Lambda函数的返回值类型。 函数体包含了Lambda函数的具体实现Lambda函数的使用可以极大地简化代码,特别是在需要传递函数对象的地方。Lambda函数可以作为函数参数、返回值或局部变量来使用。它的主要优点是简洁、灵活和易于理解。 Lambda函数在编译时会被转换为函数对象,并可以被重载、调用和赋值。Lambda函数可以在运行时根据需要生成不同的函数对象。 Lambda函数的主要原理是通过闭包来实现。闭包是指一个函数和与其相关的引用环境的组合。Lambda函数在定义时会捕获外部的变量,并生成一个与之关联的闭包对象,该闭包对象可以在函数调用时使用。 Lambda函数的实现编译器内部通过生成匿名类来实现。这个匿名类中包含了Lambda函数的操作符()的重载函数,用来实现函数的调用操作。 Lambda函数的原理虽然较为复杂,但是我们可以利用Lambda函数来简化代码,提高代码的可读性和可维护性。在使用Lambda函数时,我们可以根据需要捕获和传递外部变量,实现更灵活的代码逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值