C++ lambda 表达式 原理与应用

C++对lambda表达式的支持是从C++11版本开始,后续版本又有一些增强,本文仅根据C++11标准讨论lambda表达式的原理和应用。

定义

构造闭包:能够捕获作用域中的变量的无名函数对象。
关于C++闭包可参考:https://www.cnblogs.com/Aion/p/3449756.html

语法

[ 捕获 ] ( 形参 ) lambda说明符 约束(可选) { 函数体 }(1)
[ 捕获 ] { 函数体 }(2)(C++23 前)
[ 捕获 ] lambda说明符 { 函数体 }(2)(C++23 起)
[ 捕获 ] <模板形参> 约束(可选) ( 形参 ) lambda说明符 约束(可选) { 函数体 }(3)(C++20 起)
[ 捕获 ] <模板形参> 约束(可选) { 函数体 }(4)(C++20 起)
(C++23 前)
[ 捕获 ] <模板形参> 约束(可选) lambda说明符 { 函数体 }(4)(C++23 起)
  • 1) 完整声明。
  • 2) 省略形参列表:函数不接收实参,如同形参列表是 ()。
  • 3) 与 1) 相同,但指定泛型 lambda 并显式提供模板形参列表。
  • 4) 与 2) 相同,但指定泛型 lambda 并显式提供模板形参列表。

解释

  • 捕获:包含零或更多个捕获符的逗号分隔列表,可以 默认捕获符(capture-default) 起始。 有关捕获符的详细描述,见下文。 如果变量满足下列条件,那么 lambda 表达式在使用它前不需要先捕获:
    • 该变量是非局部变量,或具有静态或线程局部存储期(此时无法捕获该变量),或者
    • 该变量是以常量表达式初始化的引用。
      如果变量满足下列条件,那么 lambda 表达式在读取它的值前不需要先捕获:
    • 该变量具有 const 而非 volatile 的整型或枚举类型,并已经用常量表达式初始化,或者
    • 该变量是 constexpr 的且没有 mutable 成员。
  • 形参:形参列表,如在具名函数中。
  • lambda说明符:由 说明符、异常说明、属性 和 尾随返回类型 按前述顺序组成,每个组分均非必需
  • 说明符:可选的说明符的序列。不提供说明符时复制捕获的对象在 lambda 体内是 const 的。可以使用下列说明符:
    • mutable:允许 函数体 修改复制捕获的对象,以及调用它们的非 const 成员函数
  • 异常说明:为闭包类型的 operator() 提供动态异常说明或 (C++20 前) noexcept 说明符
  • 属性:为闭包类型的函数调用运算符或运算符模板的类型提供属性说明。这样指定的任何属性均属于函数调用运算符或运算符模板的类型,而非其自身。(例如不能使用 [[noreturn]])
  • 尾随返回类型:-> 返回类型,其中 返回类型 指定返回类型。如果没有 尾随返回类型,那么闭包的 operator() 的返回类型从 return 语句推导,如同对于声明返回类型为 auto 的函数的推导一样。
  • 函数体:函数体
  • <模板形参>:(角括号中的)模板形参列表,用于为泛型 lambda 提供各模板形参的名字(见下文的 闭包类型::operator())。与在模板声明中相似,模板形参列表可以后附 requires 子句,它指定各模板实参上的约束。
    模板形参列表不能为空(不允许 <>)。
  • 约束:(C++20 起)向闭包类型的 operator() 添加约束

原理

定义中说的很清楚,lambda构造了一个闭包——能够捕获作用域中的变量的无名函数对象。
首先:lambda表达式的值是一个函数对象,所以可以像使用函数对象一样使用lambda表达式构建的对象;
其次:这个函数对象捕获了其被创建时的作用域中的变量,可以在operator()实现中使用这些捕获的变量;
第三:这个函数对象无名,个人理解为其所构造的对象的类型无名,类型无法在其他地方使用,而对象本身可以用一个function对象保存。

函数对象是C++一个很基础的应用,无需多说,无名也好理解,编译器自动为我们构造了一个匿名的类并构造了这个类的一个对象,我们无需知道类名,因为与上下文关联,也就无需(不能)重复使用这个类。剩下的中点就是捕获作用域中的变量了。

语法中**[ 捕获 ]**这部分就用来说明闭包对象以何种方式捕获哪些作用域中的变量了。

关于lambda定义的闭包类型

lambda 表达式是纯右值表达式,它的类型是独有的无名非联合非聚合类类型,被称为闭包类型(closure type),它在含有该 lambda 表达式的最小块作用域、类作用域或命名空间作用域声明。闭包类型有下列成员,它们不能被显式实例化,被显式特化,或 (C++14 起)在友元声明中指名:

  • 闭包类型::operator()(形参)
  • 闭包类型::operator 返回类型(*)(形参)()
  • 闭包类型::闭包类型()
  • 闭包类型::~闭包类型()
  • 闭包类型::捕获

Lambda 捕获

捕获 是一个含有零或更多个捕获符的逗号分隔列表,可以 默认捕获符 开始。默认捕获符只有

  • &(以引用隐式捕获被使用的自动变量)和
  • =(以复制隐式捕获被使用的自动变量)。
    当出现任一默认捕获符时,都能隐式捕获当前对象(*this)。如果隐式捕获它,那么会始终以引用捕获,即使默认捕获符是 =。

捕获 中单独的捕获符的语法是

标识符(1)
标识符 ...(2)
标识符 初始化器(3) (C++14 起)
& 标识符(4)
& 标识符 ...(5)
& 标识符 初始化器(6) (C++14 起)
this(7)
* this(8) (C++17 起)
... 标识符 初始化器(9) (C++20 起)
& ... 标识符 初始化器(9) (C++20 起)
  1. 简单的以复制捕获
  2. 作为包展开的简单的以复制捕获
  3. 带初始化器的以复制捕获
  4. 简单的以引用捕获
  5. 作为包展开的简单的以引用捕获
  6. 带初始化器的以引用捕获
  7. 当前对象的简单的以引用捕获
  8. 当前对象的简单的以复制捕获
  9. 初始化器为包展开的以复制捕获
  10. 初始化器为包展开的以引用捕获

捕获列表的限制和说明:

  • 当默认捕获符是 & 时,后继的简单捕获符不能以 & 开始。
  • 当默认捕获符是 = 时,后继的简单捕获符必须以 & 开始,或者为 *this (C++17 起) 或 this (C++20 起)。
  • 任何捕获符只可以出现一次,并且名字不能与形参相同:
  • **只有定义于块作用域或默认成员初始化器中的 lambda 表达式能拥有默认捕获符或无初始化器的捕获符。**对于这种 lambda 表达式,其可达作用域(reaching scope)定义为其最内层的外围函数(及其形参)内(包含自身)的外围作用域的集合。这其中包含各个嵌套的块作用域,以及当此 lambda 为嵌套的 lambda 时也包含其各个外围 lambda 的作用域。(除了 this 捕获符之外的)任何无初始化器的捕获符中的 标识符 会使用通常的无限定名字查找在 lambda 的可达作用域中查找。查找结果必须是在可达作用域中声明的且具有自动存储期的变量,或对应变量满足这种要求的结构化绑定 (C++20 起)。该实体被显式捕获。
  • 以不带有初始化器的捕获符不能捕获类成员(如上提及,捕获符列表中只能有变量)(但可以以默认捕获符隐式捕获):
  • 如果 lambda 表达式在默认实参中出现,那么它不能显式或隐式捕获任何内容,除非所有捕获都带有完整表达式可以在默认实参中出现的初始化器 (C++14 起):
  • 当 lambda 用隐式的以复制捕获捕获某个成员时,它并不产生该成员变量的副本:对成员变量 m 的使用被处理成表达式 (*this).m,而 *this 始终被隐式以引用捕获:
  • 如果 lambda 表达式在默认实参中出现,那么它不能显式或隐式捕获任何内容,除非所有捕获都带有完整表达式可以在默认实参中出现的初始化器 (C++14 起):
  • 不能捕获匿名联合体的成员。只能以复制捕获位域。
  • 如果嵌套的 lambda m2 捕获了也被其直接外围 lambda m1 所捕获的实体,那么以如下方式将 m2 的捕获进行变换:
    • 如果外围 lambda m1 以复制捕获,那么 m2 捕获 m1 的闭包类型的非静态数据成员,而非原变量或 *this;如果 m1 非 mutable,那么认为该非静态数据成员有 const 限定。
    • 如果外围 lambda m1 以引用捕获,那么 m2 捕获原变量或 *this。

应用举例

附 后续版本中lambda表达式的变化

  1. 从C++14开始,可以自定义捕获变量并赋值
  2. 从C++17开始,支持以复制方式捕获当前对象 *this
  3. 从C++20开始,当默认捕获符为 = 时,*this 的隐式捕获被弃用。
  4. 从C++20开始,lambda表达式开始支持模板以支持泛型
  5. 从C++23开始,支持省略形参列表的情况下指定lambda说明符

参考

https://en.cppreference.com/w/cpp/language/lambda
https://www.cnblogs.com/Aion/p/3449756.html
https://blog.csdn.net/weixin_43055404/article/details/103299156

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞花丝雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值