C++ 11 中的Lambda表达式

0. functor类

首先,认识一下functor类,对于理解lambda对象会有所帮助。functor是一种行为很像函数(函数指针)的类。这种类的特点是提供了()操作符重载,可以作为函数被调用。

在使用C++标准模板库时,functor类可以捕获额外的状态(参数),提供更加灵活的用法,因此经常被使用。如下图所示的for_each函数,在特殊需求的情况下,需要自己编写定制的functor。

如下图所示的fransform函数,可以通过传值,捕获用户定义的参数,从而生成不同的函数,满足特定的需求。

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
33
34
35
36
37
// C++ program to demonstrate working of 
// functors.
#include <bits/stdc++.h>
using namespace std;

// A Functor
class increment
{
private:
int num;
public:
increment(int n) : num(n) { }

// This operator overloading enables calling
// operator function () on objects of increment
int operator () (int arr_num) const {
return num + arr_num;
}
};

// Driver code
int main()
{
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr)/sizeof(arr[0]);
int to_add = 5;

transform(arr, arr+n, arr, increment(to_add));

for (int i=0; i<n; i++)
cout << arr[i] << " ";
}

/************************************************
output:
6,7,8,9,10
************************************************/

如果该算法只用一次,这么写就显得浪费且冗余。因此考虑使用匿名函数。

1. 匿名函数

Lambda函数语法结构如图:

lambda

原理分析

lambda函数体用于functor类生成operator()方法。lambda函数是一种特殊的对象,其类型是compiler-generated,只有编译器知道。因此在声明一个lambda对象时,需要使用auto声明。

Lambda对象是一个带有作用域的functor对象,我们可以将其定义到某个算法的作用域中,这样符合好的模块设计思想。如下图:

2. 捕获对象

lambda提供了捕获列表,可以捕获作用域中的变量,并用于lambda函数体处理。有两种捕获方式:传值和传引用。和函数传值传引用一样,传值只是传递一个拷贝,传引用则是传本体。

可以使用下面两种写法,传递所有参数。

原理分析

当向捕获列表添加变量,编译器将对应的变量加入到lambda-functor类的构造函数中并作初始化。

因此,捕获列表引入了可能的消耗,每个传值的捕获变量都会引入一个新的拷贝。

3. 成员函数中的lambda

由于lambda事实上是一个独立的类,因此存在自己的作用域上下文。因此没有直接访问其他成员变量的权限。为了捕获成员变量,必须捕获该类的this指针。this指针使得lambda能够访问类的所有数据,包括私有成员。

4. 可调用对象(callable objects)

可调用对象是对所有可以像函数一样被调用的对象的统称。包括:

  1. 成员函数
  2. 普通函数
  3. functor
  4. lambda

在C语言中,有函数指针这个概念,允许函数的地址被存储。然而指向普通函数的指针和指向成员函数的指针,以及指向lambda的指针,他们的函数签名都不同。更好的解决方法是提供一个通用的类别,来存储任意可调用对象的地址。

std::function就是这样一个模板类,可以存储任意的可调用对象。std::function为可调用对象提供了一种统一的机制,用来存储,传递和访问这些对象。

这样,所有可调用对象的函数签名都变成了std::function。std::function还提供了!=运算符重载,使得可以和nullptr作比较,因此使用时如同函数指针。

如上图所示,SimpleCallback类可以被任何可调用类所使用,并不需要代码作出修改。因为他们都符合callback的函数签名。

std::function与函数指针的区别

  1. std::function 是 functor ,它可以保存一部分调用所需的额外状态(这种功能有时被称为“闭包 (closure) ”);

  2. std::function 有运行时多态,同样类型的 std::function<Ret(Args)> 对象可以处理不同类型的被调用函数和额外状态。

而函数指针只能指向同一类型的不同函数(除了 C++17 开始非 noexcept 函数指针可以指向 noexcept 函数),不能保存额外状态。

参考文献

Functors in C++

Demystifying C++ lambdas

知乎暮无井见铃的回答