您的位置:首页 > 编程语言 > C语言/C++

转:C++ Lambda表达式

2012-07-09 13:28 316 查看
Visual Studio 2010(下称VS2010)中的Visual C++编译器包含了对4项(正式发布后可能更多)C++0x 特性的支持,分别为lambda表达式auto关键字static_assert,和右值引用(rvalue references)。此篇文章将对前三项进行详细解释,rvalue references将在后续文章中解释。

相关资料:
C++0x language feature status:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2705.html
C++0x library feature status:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2706.html
C++0x Working Draft:
http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2798.pdf

Lambda表达式

在C++0x中“lambda表达式”隐式的定义并构造了一个匿名的函数对象,它和普通的函数对象的行为是类似的。下面是“Hello World”的lambda:

#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;

int main()
{
vector<int> v;
for (int i = 0; i < 10; ++i)
{
v.push_back(i);
}
for_each(v.begin(),v.end(),[](int i) { cout<<i<<ends; } );
}


代码中的“[]”叫做“lambda前导符”,它告诉编译器一个lambda表达式开始了。而“(int n)”是lambda表达式中的参数声明部分,它告诉编译器,这个匿名的函数对象中,函数调用运算符(“()”运算符)调用时接受的参数。最后“{ cout << n << " "; }”大括号语句作为这个匿名函数对象的函数调用符的函数体。默认情况下,匿名函数对象的返回值为void。

因此,上面的C++0x代码实质上等价于下面的C++98代码:

#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;

struct LambdaFunctor {
void operator()(int n) const {
cout << n << " ";
}
};

int main() {
vector<int> v;

for (int i = 0; i < 10; ++i) {
v.push_back(i);
}

for_each(v.begin(), v.end(), LambdaFunctor());
cout << endl;
}


现在,我不再说“那个匿名的函数对象的函数调用运算符的返回值是void”,而是直接说“这个lambda返回void”,但是,非常重要的一点是:一定要记住lambda表达式干了什么:定义了一个类(LambdaFunctor),然后构造了它的对象

当然了,lambda的大括号表达式中,可以包含多个语句:

#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;

int main() {
vector v;

for (int i = 0; i < 10; ++i) {
v.push_back(i);
}

for_each(v.begin(), v.end(), [](int n) {
cout << n;

if (n % 2 == 0) {
cout << " even ";
} else {
cout << " odd ";
}
});

cout << endl;
}


0 even 1 odd 2 even 3 odd 4 even 5 odd 6 even 7 odd 8 even 9 odd

现在,lambda也不必总是返回void,如果一个lambda的大括号表达式为{ return expression; },那么这个lambda的返回值类型将被自动推断为expression的类型.

#include <algorithm>
#include <deque>
#include <iostream>
#include <iterator>
#include <ostream>
#include <vector>
using namespace std;

int main() {
vector<int> v;

for (int i = 0; i < 10; ++i) {
v.push_back(i);
}

deque<int> d;

transform(v.begin(), v.end(), front_inserter(d), [](int n) { return n * n * n; });

for_each(d.begin(), d.end(), [](int n) { cout << n << " "; });
cout << endl;

}


729 512 343 216 125 64 27 8 1 0

这里,n * n * n的类型为int,所以,lambda返回值类型为int。

有些更加复杂的lambda表达式无法做出类型推断,你需要显式的指定它:

#include <algorithm>
#include <deque>
#include <iostream>
#include <iterator>
#include <ostream>
#include <vector>
using namespace std;

int main() {
vector<int> v;

for (int i = 0; i < 10; ++i) {
v.push_back(i);
}

deque<int> d;

transform(v.begin(), v.end(), front_inserter(d), [](int n) -> double {
if (n % 2 == 0) {
return n * n * n;
} else {
return n / 2.0;
}
});

for_each(d.begin(), d.end(), [](double x) { cout << x << " "; });
cout << endl;
}


照道理应该输出:

4.5 512 3.5 216 2.5 64 1.5 8 0.5 0

可是vs2010输出了:

4 512 3 216 2 64 1 8 0 0
请按任意键继续. . .

说明vs2010对lambda的支持还不是很好

“-> double”是可选的“lambda返回值类型从句”。为什么返回值类型声明没有像以前声明函数的时候放在前面呢?因为如果不把“[]”放在最前面的话,编译器将无法知道这是一个lambda表达式。

这里,如果你忘记了声明返回值类型,编译器将会报告错误:

error C3499: a lambda that has been specified to have a void return type cannot return a value

vs2010: error C3499: 已指定返回类型为 void 的 lambda 无法返回值

上面列出的所有lambda都是无状态的:它们构造出来的匿名函数对象不包含任何数据成员。你也可以获得有状态的lambda,方法是通过“捕获”局部变量。空白的lambda前导符“[]”表示这是一个无状态的lambda,但是在“[]”内部,你可以指定一个捕获组:

#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;

int main() {
vector<int> v;

for (int i = 0; i < 10; ++i) {
v.push_back(i);
}

int x = 0;
int y = 0;

// op>>()不会读取输入流中最后一个回车换行符,
// 这有时候会带来很多问题。我推荐
// 避免使用它,而使用非成员函数
// getline(cin, str)读取一整行,
// 然后再解析它。但是为了简洁起见,
// 我会继续使用 op>>():

cout << "Input: ";
cin >> x >> y;

v.erase(remove_if(v.begin(),v.end(),[x,y](int n) { return x<n&&y>n; } ),v.end());

for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
cout << endl;
}


Input: 4 7
0 1 2 3 4 7 8 9 (因为移除了4到7之间的元素)

如果你忘记了写捕获组,编译器会报告错误:

error C3493: 'x' cannot be implicitly captured as no default capture mode has been specified

(一会儿我将会介绍默认捕获)

记住,lambda表达式隐式的定义了一个匿名的函数对象,“{ return x < n && n < y; }”就是它函数调用符的函数体,所以尽管看起来大括号语句是在main()的范围内,但在概念上它是在main()函数范围之外的,lambda表达式中不能使用没有捕获的main()函数内的局部变量。

这是把上面程序等价转换后的代码:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <ostream>
#include <vector>
using namespace std;

class LambdaFunctor {
public:
LambdaFunctor(int a, int b) : m_a(a), m_b(b) { }

bool operator()(int n) const { return m_a < n && n < m_b; }

private:
int m_a;
int m_b;
};

int main() {
vector<int>  v;

for (int i = 0; i < 10; ++i) {
v.push_back(i);
}

int x = 0;
int y = 0;

cout << "Input: ";
cin >> x >> y; // EVIL!

v.erase(remove_if(v.begin(), v.end(), LambdaFunctor(x, y)), v.end());

copy(v.begin(), v.end(), ostream_iterator(cout, " "));
cout << endl;
}


Input: 4 7
0 1 2 3 4 7 8 9

这里,你可以清楚的看到捕获变量是按值传递的。局部变量的拷贝被存储在了函数对象中。这样,函数对象存活的时间可以比被捕获的局部变量长。但是,需要注意几点:

(a)捕获的变量在lambda中不可修改,因为默认情况下生成的是const函数调用运算符;

(b)有些对象的复制操作开销比较大;

(c)局部变量的更新将不会影响lambda内已经被捕获过的副本(这符合通常的值传递语义)。一会儿,我将解释在需要的时候如何应对上述三种情况。

但是首先,如果你想捕获所有的局部变量,可以不必逐项列出它们,而只需告诉编译器“按值传递捕获所有变量”。实现方式是使用前导符“[=]”:

#include <algorithm>
#include <iostream>
#include <ostream>
#include <vector>
using namespace std;

int main() {
vector v;

for (int i = 0; i < 10; ++i) {
v.push_back(i);
}

int x = 0;
int y = 0;

cout << "Input: ";
cin >> x >> y; // EVIL!

v.erase(remove_if(v.begin(), v.end(), [=](int n) { return x < n && n < y; }), v.end());

for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });
cout << endl;
}


Input: 4 7
0 1 2 3 4 7 8 9

当编译器看到lambda中的x和y,会将它们从main()中按值捕获进来。

对于情况(a)无法修改捕获的变量,如何处理呢?默认情况下,lambda的函数调用运算符是const的,但是你可以通过mutable关键字将其变为非const:

http://hi.baidu.com/icexile/blog/item/a9836460f05fcbd58cb10dd0.html

http://technet.microsoft.com/zh-cn/subscriptions/dd293603(v=vs.110).aspx

http://developer.51cto.com/art/201207/345807.htm

http://www.devbean.info/2012/05/cpp11-lambda/

http://www.cppblog.com/len/archive/2011/04/22/50286.html

/article/4831255.html

上面网址部分内容:

C++11 的 lambda 表达式规范如下:

[
capture
]
(
params
)
mutable exception attribute
->
ret
{
body
}
(1)
[
capture
]
(
params
)
->
ret
{
body
}
(2)
[
capture
]
(
params
)
{
body
}
(3)
[
capture
]
{
body
}
(4)
其中

(1) 是完整的 lambda 表达式形式,

(2) const 类型的 lambda 表达式,该类型的表达式不能改捕获("capture")列表中的值。

(3)省略了返回值类型的 lambda 表达式,但是该 lambda 表达式的返回类型可以按照下列规则推演出来:

如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。

如果没有 return 语句,则类似 void f(...) 函数。

省略了参数列表,类似于无参函数 f()。

mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法。

exception 说明 lambda 表达式是否抛出异常(
noexcept
),以及抛出何种异常,类似于void f() throw(X, Y)。

attribute 用来声明属性。

另外,capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表,具体解释如下:

[a,&b]
a变量以值的方式呗捕获,b以引用的方式被捕获。

[this]
以值的方式捕获 this 指针。

[&]
以引用的方式捕获所有的外部自动变量。

[=]
以值的方式捕获所有的外部自动变量。

[]
不捕获外部的任何变量。

例子:

#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>

int main()
{
std::vector<int> c { 1,2,3,4,5,6,7 };
int x = 5;
c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; } ), c.end());

std::cout << "c: ";
for (auto i: c) {
std::cout << i << ' ';
}
std::cout << '\n';

// the type of a closure cannot be named, but can be inferred with auto
auto func1 = [](int i) { return i+4; };
std::cout << "func1: " << func1(6) << '\n';

// like all callable objects, closures can be captured in std::function
// (this may incur unnecessary overhead)
std::function<int(int)> func2 = [](int i) { return i+4; };
std::cout << "func2: " << func2(6) << '\n';
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: