C++ 中的 Lambda 表达式
前些天买了本《程序设计语言理论》,看了简介,Lambda 演算贯穿整个理论,尤其在函数式语言中具有重要作用。C++11 中也加入了 Lambda 表达式,下面做个总结。
1. 一个简单的例子
一个简单的 Lambda 表达式如下:
[] {};
这就定义了一个对象,这个对象匿名,再强调一下,Lambda 表达式是对象,不是类型。本例中,该对象的类型是 ‘anonymous-namespace’::<lambda0>,这是编译器给它设的一个类型名。
2. 实现
一般地,编译器实现 Lambda 表达式时,将其转化为函数对象(仿函数 functor)。比如上面的例子将转化为
struct A {
public:
void operator()() const {}
};
A();
其中 A() 是一个函数对象,而 A 是类型,这些要明确区分。
3. 状态
仿函数和 Lambda 表达式是可以有状态的,也就是说它们可以含数据成员,而函数指针就无状态可言,比如下面这种情况就无法用函数指针,只能用仿函数或 Lambda 表达式。
/********************************************************************
created: 2014/05/07 22:31
filename: main.cpp
author: Justme0 (http://blog.csdn.net/justme0)
purpose: 涉及状态时,只能用仿函数或 Lambda 表达式,无法用函数指针
*********************************************************************/
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/*
** 输出 vec 中大于 standard 的元素
*/
void print_use_lambda(const vector<int> &vec, int standard) {
for_each(vec.begin(), vec.end(), [standard](int elem) {
if (standard < elem) {
cout << elem << ' ';
}
});
}
struct Printor {
private:
int m_standard;
public:
Printor(int standard) : m_standard(standard) {}
void operator()(int elem) const {
if (m_standard < elem) {
cout << elem << ' ';
}
}
};
/*
** 输出 vec 中大于 standard 的元素
*/
void print_use_functor(const vector<int> &vec, int standard) {
for_each(vec.begin(), vec.end(), Printor(standard));
}
int main(int argc, char **argv) {
int arr[] = {5, 2, 1, 4, 5, 6, 7, 2};
int size = sizeof arr / sizeof *arr;
vector<int> vec(arr, arr + size);
cout << "需要输出大于几的数?" << endl;
int standard;
cin >> standard;
print_use_functor(vec, standard);
cout << endl;
print_use_lambda(vec, standard);
cout << endl;
system("PAUSE");
return 0;
}
程序中调用 for_each 时,只输出大于某个数的元素,而这个数事先不知道,这个数代表了仿函数的状态,而若用函数指针则无法获得这个数(状态),注意 for_each 的第三个参数是一元的。
4. 捕捉列表中的值传递与引用传递
按值方式传递捕捉列表与按引用方式传递列表不同,以下面的程序为例:
/********************************************************************
created: 2014/05/06 21:01
filename: main2.cpp
author: Justme0 (http://blog.csdn.net/justme0)
purpose: Lambda 的捕捉列表
*********************************************************************/
#include <iostream>
using namespace std;
int main(int argc, char **argv) {
int i = 0;
auto by_val_lambda = [i] {
return i;
};
auto by_ref_lambda = [&i] {
return i;
};
cout << by_val_lambda() << endl; // 0
cout << by_ref_lambda() << endl; // 0
++i;
cout << by_val_lambda() << endl; // 0
cout << by_ref_lambda() << endl; // 1
system("PAUSE");
return 0;
}
要解释这个问题,把它们转化为仿函数(编译器的实现)就一目了然了:
/********************************************************************
created: 2014/05/06 20:39
filename: main.cpp
author: Justme0 (http://blog.csdn.net/justme0)
purpose: 用 Functor 代替 Lambda
*********************************************************************/
#include <iostream>
using namespace std;
struct Value {
private:
int m_i;
public:
Value(int i) : m_i(i) {}
int operator()() const {
return m_i;
}
};
struct Reference {
private:
int &m_i;
public:
Reference(int &i) : m_i(i) {} // 必须用初始化列表
int operator()() const {
return m_i;
}
};
struct Pointer {
private:
int *m_pi;
public:
Pointer(int *pi) : m_pi(pi) {}
int operator()() const {
return *m_pi;
}
};
int main(int argc, char **argv) {
int i = 0;
auto by_val_functor = Value(i);
auto by_ref_functor = Reference(i);
auto by_ptr_functor = Pointer(&i);
cout << by_val_functor() << endl; // 0
cout << by_ref_functor() << endl; // 0
cout << by_ptr_functor() << endl; // 0
++i;
cout << by_val_functor() << endl; // 0
cout << by_ref_functor() << endl; // 1
cout << by_ptr_functor() << endl; // 1
system("PAUSE");
return 0;
}
值传递时 Lambda 保存了一个副本,与形参就无关了。引用传递时,Lambda 保存的是形参的引用,所以后面改变形参 ++i 时,Lambda 内保存的引用指向的 i 变了(同一个 i),注意指针与引用类似,用指针来解释引用再好不过了。
5. 捕捉列表的限制
Lambda 捕捉列表仅能捕捉父作用域的自动变量。
标准是这么定义的,但有的编译器不遵守,笔者对具体的规则也存有疑惑。但总的来说,Lambda 的思想是局限于一个局部作用域中使用,若需全局共享,则用函数指针(无状态)或仿函数(有状态)。
参考资料:《深入理解 C++11》
(End)