lambda
1. 仿函数(函数对象)、闭包closure、closure对象
- 仿函数functors
仿函数,也称函数对象function object,类中重载operator()操作符,使得类的使用看上去像一个函数,通常用于STL算法中的最后一个自定义参数。
// 仿函数
class Test {
public:
Test(int value) : value_(value) {};
bool operator() (int x)
{
return x > value_;
}
private:
int value_;
};
- 闭包closure、闭包对象
闭包可以认为是一组变量和函数组成的类。 lambda
实质上可以看成一个匿名函数,而lambda
对外部变量的捕获后称为closure
,lambda表达式的底层实现称为closure
对象。 lambda按值捕获会给人一种闭包自足的情况,但是按值捕获指针如this的时候也需要关注this的生命周期,按值捕获时lambda使用static变量时也和外界参数有关,不能说值捕获就是不依赖外界的闭包自足情况。
2. lambda语法
[捕获列表] (参数列表) -> 返回类型 {函数体}
- 捕获列表: 实现闭包功能,可以捕获外部变量供lambda使用
- 捕获方式:默认值捕获,显示值捕获为
[=value]
, 引用捕获为[&value]
, 捕获多个变量且捕获方式不同[=value1, &value2]
, 全部值捕获[=]
, 全部引用捕获[&]
- 注意1:按引用捕获时需要注意引用的变量生命周期和lambda被使用的生命周期
- 注意2:lambda捕获的对象只能是lambda作用域内的可见的非静态局部变量,包括参数,特别注意在类成员函数中的lambda不能直接捕获类的成员变量,因为有隐式的this捕获存在,如果有捕获变量声明周期危险,可以定义新的局部变量 = 类成员变量,再捕获本地新局部变量。
- 注意3:c++11不支持移动捕获(初始化捕获),c++14可以在捕获列表中使用
[value = std::move(outValue)]
的方式实现移动捕获。如果不支持c++14,可以用老方法,仿函数(函数对象)来实现移动捕获,在构造对象的时候将数据移动给类变量;也可以使用bind方式,将数据移动至bind对象中,再通过引用传递给lambda。 - 注意4:lambda生成的闭包类中operator()函数是const的,通过bind引用变量如果是非const的,需要去掉lambda的const属性,通过
[]() mutable {}
实现
- 捕获方式:默认值捕获,显示值捕获为
// c++11
std::vector<double> data;
auto func = std::bind([](std::vector<double> &data) mutable {
}, std::move(data));
// 等同于c++14 初始化捕获
auto func = [data = std::move(data)]() {
};
// c++11
auto func2 = std::bind([](const TSome &data) {
}, make_unique<TSome>());
// 等同于c++14 初始化捕获
auto func2 = [data = make_unique<TSome>()]() {
};
- 参数列表:和普通函数列表一致
- c++14支持auto泛型参数,相当于仿函数operator函数为模板函数. 完美转发实现万能引用时,转发模板T由于auto无法推到T是啥,故使用
decltype(param)
得出,decltype(param)
如果param是左值引用则返回左值引用,如果param是右值引用则返回右值引用。
- c++14支持auto泛型参数,相当于仿函数operator函数为模板函数. 完美转发实现万能引用时,转发模板T由于auto无法推到T是啥,故使用
// c++14
auto f = [](auto &&x){
return func(normalize(std::forward<decltype(param)>(x)));
};
// c++14 变参
auto func = [] (auto&& ...params) {
return func(normalize(std::forward<decltype(params)>(params)...));
}
// 仿函数不用auto,直接可以std::forward<T>(x)
class Test {
public:
template<typename T>
auto operator(T&& x) const
{
return func(normalize(std::forward<T>(x)));
}
};
- 返回类型:和普通返回类型一致,可以省略
->返回类型
,自动推到返回类型 - 函数体:可以操作参数列表和捕获列表中的变量
3. 例子:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 仿函数
class Test {
public:
Test(int value) : value_(value) {};
bool operator() (int x)
{
return x > value_;
}
private:
int value_;
};
int main()
{
vector<int> v{1, 32, 4512, 5, 32, 44};
int value = 4;
// 没有lambda的时候, 仿函数/函数对象实现
cout << count_if(v.begin(), v.end(), Test(4)) << endl; // 5
// lambda
cout << count_if(v.begin(), v.end(), [value](int x) {
return x > value;
}) << endl; // 5
// 引用捕获
auto func = [&value](int x){ return x > value;};
cout << count_if(v.begin(), v.end(), func) << endl; // 5
value = 32;
cout << count_if(v.begin(), v.end(), func) << endl; // 2
return 0;
}
备注
- closure: 闭包可以理解成"定义在一个函数内部的函数"。在本质上,闭包是将函数内部和函数外部连接起来的桥梁,闭包技术使得内部函数可以使用外部函数的局部变量。
- 仿函数:实质是类重载operator()后的结果,使用过程中看起来像函数调用,有自己的类成员变量。
- std::fucntion:通用函数包装器,可以存储、赋值、调用任何可调用的目标:包括函数、lambda、bind表达式、成员函数等。
- bind:参数绑定,c++11前用的比较多,c++11大部分场景用lambda代替,只有在移动捕获和模板参数时先用bind参数后再传给lambda,c++14后lambda支持auto后bind几乎不需要使用。
- lambda:便捷匿名函数,易用的参数绑定。