文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

编译器如何实现lambda表达式?

2024-12-03 03:23

关注

本文转载自微信公众号「程序喵大人」,作者程序喵大人。转载本文请联系程序喵大人公众号。

lambda表达式在C++11中引入,用lambda表达式表示匿名函数非常方便,语法很简单,而且可以使代码更紧凑,更易于阅读。

lambda表达式更适合定义小点的回调内联去传递给其他函数,而不是在其他地方定义个完整的函数对象,并在其重载函数调用运算符中实现回调逻辑。所有的逻辑都在一个位置上,容易理解和维护,lambda表达式可以接收参数,可返回值,可模板化,可通过值或引用的方式访问外面的变量,相当的灵活。

关于lambda表达式的使用,我之前介绍过,可以看这篇文章搞定c++11新特性std::function和lambda表达式,这里一笔带过:

  1. auto lambda { []{ cout << "Hello \n"; } }; 
  2. lambda(); 

那这个lambda表达式是如何实现的呢?

编译器会将lambda表达式自动转换为函数对象,编译器会为此生成个唯一的命名。上面的示例会自动的转换成下面这样的函数对象,注意函数调用运算符是个const方法,返回类型是auto,这方便编译器根据方法体自动推导出返回类型。

  1. class CompilerGeneratedName { 
  2.     public
  3.         auto operator()() const { cout << "Hello \n"; } 
  4. }; 

编译器生成的lambda闭包名字会是一些奇怪的名子,例如__Lambda_21Za等,我们没法知道这个名字,我们也不需要知道这个名字。

lambda表达式可以接收参数,参数在圆括号之间指定,就像普通函数一样,下面是例子:

  1. auto lambda { 
  2.     [](int value){ cout << "The value is " << value << endl; } }; 
  3. lambda(42); 

如果lambda表达式不接收任何参数,可以指定空括号或者直接省略括号。

编译器会将上面的lambda表达式自动转换为下面这样:

  1. class CompilerGeneratedName { 
  2.     public
  3.         auto operator()(int value) const { 
  4.             cout << "The value is " << value << endl; } 
  5. }; 

lambda表达式可以返回值,返回类型在箭头后面指定,称为尾返回类型,看代码:

  1. auto lambda { [](int a, int b) -> { return a + b; } }; 
  2. int sum = lambda(11, 22); 

编译器转成这样:

  1. class CompilerGeneratedName { 
  2.     public
  3.         auto operator()(int a, int b) const { return a + b; } 
  4. }; 

那能捕获变量的lambda表达式是怎么实现的呢?

比如下面的lambda表达式:

  1. double data { 1.234 }; 
  2. auto lambda { [data]{ cout << "Data = " << data << endl; } } 

捕获的变量会变为lambda闭包的数据成员,值捕获的变量被拷贝到仿函数的数据成员中,编译器的行为是这样:

  1. class CompilerGeneratedName 
  2.     public
  3.         CompilerGeneratedName(const double& d) : data { d } {} 
  4.         auto operator()() const { cout << "Data = " << data << endl; } 
  5.     private: 
  6.         double data; 
  7. }; 

还有泛型lambda表达式:

  1. auto areEqual { [](const auto& value1, const auto& value2) { 
  2.     return value1 == value2; } }; 
  3.  
  4. vector values1 { 2, 5, 6, 9, 10, 1, 1 }; 
  5. vector values2 { 4, 4, 2, 9, 0, 3, 1 }; 
  6. findMatches(values1, values2, areEqual, printMatch); 

编译器会转换成这样:

  1. class CompilerGeneratedName { 
  2.     public
  3.         template  
  4.         auto operator()(const T1& value1, const T2& value2) const 
  5. return value1 == value2; } 
  6. }; 

如果findMatches()函数中的参数是其他类型,那么areEqual泛型表达式不需要任何更改就可以直接继续使用。

聊完了编译器怎么实现的lambda表达式,下面介绍下lambda表达式的捕获方式。

捕获方式

有两种方法从闭包作用域捕获所有变量,称为默认捕获:

再注意:全局变量总是通过引用捕获,例如在下面的代码中,默认捕获用于按值捕获所有内容,然而全局变量global其实是通过引用捕获的,在执行lambda 后它的值被更改。

  1. int global { 42 }; 
  2. int main() { 
  3.     auto lambda { [=] { global = 2; } }; 
  4.     lambda(); 
  5.     // 这里global是2! 

不允许像下面这样显式捕获全局变量,这样编译会失败:

  1. auto lambda { [global] { global = 2; } }; // error 

所以,建议不要使用全局变量。

对于不捕获任何内容的lambda表达式,编译器自动提供转换运算符,将lambda 表达式转换为函数指针。这样的lambda表达式可作为参数传递给其他函数。

在C++20中关于lambda表达式也做了一些更新,可以模板化lambda表达式,也可以默认构造、拷贝和赋值lambda表达式,像下面这样:

  1. auto lambda { [](int a, int b) { return a + b; } }; 
  2. decltype(lambda) lambda2; // 默认构造 
  3. auto copy { lambda }; // 拷贝构造 
  4. copy = lambda2; // 拷贝赋值 

 

这不是本文的主题,就不过多介绍了。

 

来源:程序喵大人内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯