0. functor类
首先,认识一下functor类,对于理解lambda对象会有所帮助。functor是一种行为很像函数(函数指针)的类。这种类的特点是提供了()操作符重载,可以作为函数被调用。
在使用C++标准模板库时,functor类可以捕获额外的状态(参数),提供更加灵活的用法,因此经常被使用。如下图所示的for_each函数,在特殊需求的情况下,需要自己编写定制的functor。
如下图所示的fransform函数,可以通过传值,捕获用户定义的参数,从而生成不同的函数,满足特定的需求。
1 | // C++ program to demonstrate working of |
如果该算法只用一次,这么写就显得浪费且冗余。因此考虑使用匿名函数。
1. 匿名函数
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)
可调用对象是对所有可以像函数一样被调用的对象的统称。包括:
- 成员函数
- 普通函数
- functor
- lambda
在C语言中,有函数指针这个概念,允许函数的地址被存储。然而指向普通函数的指针和指向成员函数的指针,以及指向lambda的指针,他们的函数签名都不同。更好的解决方法是提供一个通用的类别,来存储任意可调用对象的地址。
std::function就是这样一个模板类,可以存储任意的可调用对象。std::function为可调用对象提供了一种统一的机制,用来存储,传递和访问这些对象。
这样,所有可调用对象的函数签名都变成了std::function。std::function还提供了!=运算符重载,使得可以和nullptr作比较,因此使用时如同函数指针。
如上图所示,SimpleCallback类可以被任何可调用类所使用,并不需要代码作出修改。因为他们都符合callback的函数签名。
std::function与函数指针的区别
std::function 是 functor ,它可以保存一部分调用所需的额外状态(这种功能有时被称为“闭包 (closure) ”);
std::function 有运行时多态,同样类型的 std::function<Ret(Args)> 对象可以处理不同类型的被调用函数和额外状态。
而函数指针只能指向同一类型的不同函数(除了 C++17 开始非 noexcept 函数指针可以指向 noexcept 函数),不能保存额外状态。