C++重要的现代特性之一是lambda表达式,也称为lambda函数或lambda。lambda表达式使我们能够定义匿名函数对象,这些对象可以捕获作用域中的变量,并且可以被调用或作为函数的参数传递。因此,lambda在很多方面都很有用。在本节中,我们将学习如何在标准算法中使用它们。
在本节中,我们将讨论标准算法,它接受一个参数,即应用于它所迭代的元素的函数或谓词。你需要知道什么是一元函数和二元函数,什么是谓词和比较函数,你还需要熟悉函数对象,因为lambda表达式是函数对象的语法糖。
在标准算法中,应该使用lambda表达式而不是函数或函数对象来传递回调:
❍ 如果只需要在单个位置使用lambda,请在调用位置定义匿名lambda表达式:
❍ 如果需要在多个位置调用lambda,则定义命名lambda,即赋值给变量的lambda(通常使用类型的auto标识符):
❍ 如果需要的lambda仅在参数类型方面不同,则使用泛型lambda表达式(从C++14起可用):
第二个示例中显示的非泛型lambda表达式接受一个常量整数,如果它大于0,则返回true,否则返回false。编译器用operator调用操作符定义一个未命名的函数对象,该对象为lambda表达式的签名:
编译器定义未命名函数对象的方式取决于我们定义lambda表达式的方式,该表达式可以捕获变量、使用mutable说明符或异常规范,或者具有尾部返回类型。前面示例的__lambda_name__函数对象实际上是编译器生成的内容的简化,因为它还定义了默认的复制构造函数和移动构造函数、默认的析构函数和已删除的赋值操作符。
lambda表达式实际上是一个类,为了调用它,编译器需要实例化类的对象。从lambda表达式实例化的对象称为lambda闭包。
在下面的示例中,我们要计算大于或等于5且小于或等于10的范围内的元素数,lambda表达式如下所示:
这个lambda通过复制(即值传递)捕获两个变量:minimum和maximum。编译器创建的未命名函数对象与我们前面定义的函数对象非常相似。使用前面提到的默认特殊成员和已删除的特殊成员,该类看起来如下所示:
lambda表达式可以通过复制(值传递)或引用捕获变量,也可以采用这两者的不同组合方式。但是,不能多次捕获一个变量,只能在捕获列表的开头使用&或=。
lambda只能从封闭函数作用域捕获变量。它不能捕获具有静态存储周期的变量(即在命名空间作用域中或使用static或external说明符声明的变量)。
表3.1展示了lambda捕获语义的各种组合。
表3.1
(续)
从C++17开始,lambda表达式的一般形式如下:
此语法中显示的所有部分实际上都是可选的,但捕获列表和主体除外,捕获列表可以为空,主体也可以为空。如果不需要参数,则可以省略参数列表。不需要指定返回类型,因为编译器可以从返回表达式的类型推导出它。mutable说明符(指示编译器lambda实际上可以修改通过复制捕获的变量)、constexpr说明符(指示编译器生成constexpr调用操作符)以及异常说明符和属性都是可选的。
最简单的lambda表达式是[]{},但它通常写成[](){}。
表3.1中的后两个示例是广义lambda捕获形式,它们是在C++14中引入的,允许我们捕获仅支持移动语义的变量,但它们也可以用于在lambda中定义新的对象。以下示例展示了广义lambda如何用move捕获变量:
在类方法中编写并需要捕获类数据成员的lambda,可以通过以下几种方式实现:
❍ 使用[x=expr]的形式捕获单个数据成员:
❍ 使用[=]形式捕获整个对象(注意,通过[=]隐式捕获指针this在C++20中已弃用):
❍ 通过捕获this指针来捕获整个对象,如果需要调用该类的其他方法,这是必需的。当指针被按值捕获时,可以将其捕获为[this];当对象本身被按值捕获时,可以将其捕获为[*this]。如果在捕获发生后但在调用lambda之前对象可能会超出作用域,那么这将产生很大的区别:
在后一种情况中,正确的捕获应该是[*this],以便按值复制对象。在本例中,调用lambda将打印42 john,即使临时变量已超出作用域。
C++20标准对捕获指针this进行了以下几项更改:
❍ 它不赞成在使用[=]时隐式捕获this,编译器会产生一个废弃警告。
❍ 当想用[=,this]捕获所有内容时,它引入了显式的this指针值捕获,我们仍然只能用[this]捕获指针this。
在某些情况下,lambda表达式仅在参数方面有所不同。在这种情况下,lambda可以像模板一样以泛型方式编写,但要使用类型参数的auto标识符(不涉及模板语法),这将在下一节中提及,如3.2.4节所述。
❍ 阅读3.3节,以了解如何为lambda参数使用auto关键字,以及如何在C++20中定义模板lambda。
❍ 阅读3.4节,以了解递归调用lambda本身的技术。