来源: <http://www.cnblogs.com/brucejia/archive/2009/09/05/1560675.html>
【本文大部分内容译自Visual C++ Team Blog】http://blogs.msdn.com/vcblog/archive/2008/10/28/lambdas-auto-and-static-assert-c-0x-features-in-vc10-part-1.aspx
尽管C++社区对C++ 0x很是追捧,但是各厂商对于新标准的支持并不热乎。盼星星盼月亮,微软作为Windows平台上最强势的C++编译器厂商也终于在Visual Studio 2010中开始支持C++ 0x的特性。
Lambda表达式,auto 和静态断言(static_assert)
Visual Studio 2010中的Visual C++编译器,即VC10, 包含了4个C++ 0x的语言特性 - lambda表达式,auto,static_assert 和 rvalue reference (右值引用).
相关链接:
- 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
lambdas
使用过函数式编程语言(如lisp, F#)或一些动态语言(如Python,Javascript)的大侠对于lambda表达式一定不会陌生。
在C++ 0x中,引入了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);
}
for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "; });
cout << endl;
return
0;
}
C:\Temp>cl /EHsc /nologo /W4 meow.cpp > NUL && meow
0 1 2 3 4 5 6 7 8 9
for_each一行中,中括号[]称为lambda introducer, 它告诉编译器接下来的是一个lambda表达式;接下来(int n)是lambda表达式的参数声明;最后大括号里边就是“函数体”了。
注意这里因为lambda表达式生成的是functor,所以“函数体”实际上是指这个functor的operator ()的调用部分。你也许会问:那么返回值呢?缺省情况下lambda表达式生成的functor调用
返回类型为void.
所以,可以理解为上边的代码会被编译器翻译成如下:
<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;
return
0;
}
请大家一定记住:lambda表达式生成了类型,并构造该类型的实例。
下面的例子中lambda表达式的“函数体”包含多条语句:
<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
n) {
cout << n;
if
(n % 2 == 0) {
cout <<
" even ";
}
else {
cout <<
" odd ";
}
});
cout << endl;
return
0;
}
答案是:lambda表达式的“函数体”中如果有一个return的表达式,例如{ return expression; },那么编译器将自动推演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;
}
上例中返回值n * n * n很简单,类型推演是显而易见的。但是如果lambda表达式中有非常复杂的表达式时,编译器可以无法推演出其类型,或者是推演出现二义性,这时候你可以
显式地指明返回值类型。如下所示:
[](int
n) ->
double {
if
(n % 2 == 0) {
return
n * n * n;
}
else {
return
n / 2.0;
}
});
黑体部分中有的“-> double”显式地指明了lambda表达式的返回类型是double.
以上例子中的lambda都是无状态的(stateless),不包含任何数据成员。很多时候我们需要lambda包含数据成员以保存状态,这一点可以通过“捕获”(capturing)局部变量来实现。
lambda表达式的导入符(lambda-introducer)是空的,也就是“[]”,表明该lambda是一个无状态的。但是在lambda导入符中可以指定一个“捕获列表”(capture-list)。
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;
v.erase(remove_if(v.begin(), v.end(), [x, y](int
n) {
return x < n && n < y; }), v.end());
for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "; });
cout << endl;
}
上边的代码中的lambda使用了局部变量x和y,将值介于x和y之间的元素从集合中删除。
程序运行示例如下 -
Input: 4 7
0 1 2 3 4 7 8 9
上边的代码可以理解为:
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;
v.erase(remove_if(v.begin(), v.end(), LambdaFunctor(x, y)), v.end());
copy(v.begin(), v.end(), ostream_iterator<int>(cout,
" "));
cout << endl;
}
上面代码中很重要的一点信息是:lambda中捕获的局部变量是以“传值”的方式传给匿名函数对象的。在匿名函数对象中,保存有“捕获列表”中局部变量的拷贝。
这一点使得匿名函数对象的生命周期能够长于main中的x,y局部变量。然而这样的传值方式带来几个限制:
- lambda中的这两个拷贝并不能被改变,因为缺省情况下函数对象的operator()是const;
- 有的对象的拷贝操作开销很大或者不可能(例如如果上面代码中的x, y是数据库链接或者某个singleton)
- 即使在lambda内部修改了m_a, m_b也不能够影响外边main函数中的x和y
既然有了“传值”,你一定猜到了还会有“传引用”。bingo! 你是对的。
在讨论“传引用”之前,我们先来看看另一个比较有用的东西。假设你有一大堆的局部变量需要被lambda使用,那么你的“捕获列表”将会写的很长,这肯定不是件愉快的事情。
好在C++委员会的老头们也想到了,C++ 0x中提供了一个省心的东西:如果捕获列表写成 [=],表示lambda将捕获所有的局部变量,当然也是传值方式。这种方式姑且被称为“缺省捕获”(capture-default)。
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(), [=](int
n) {
return x < n && n < y; }), v.end());
for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "; });
cout << endl;
}
当编译器在lambda的作用范围内看到局部变量x, y时,它会以传值的方式从main函数中将他们捕获。
下面我们来看如何突破前面提到的3点限制。
第一点,修改lambda表达式中的局部变量拷贝(e.g. m_a, m_b)
缺省情况下,lambda的operator ()是const 修饰的,但是你可以使用mutable关键字改变这一点。
main() {
vector<int> v;
for
(int
i = 0; i < 10; ++i) {
v.push_back(i);
}
int
x = 1;
int
y = 1;
for_each(v.begin(), v.end(), [=](int& r)
mutable {
const int
old = r;
r *= x * y;
x = y;
y = old;
});
for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "; });
cout << endl;
cout << x <<
", " << y << endl;
}
代码运行结果如下
0 0 0 6 24 60 120 210 336 504
1, 1
这里我们解决了第一个限制,但是却产生了一个新的限制
4. lambda中对捕获变量的修改并不会影响到main函数中的局部变量,因为lambda捕获局部变量使用的是传值方式
下面该“传引用”的方式登场了,它能够有效地解决2,3,4三个限制。
传引用的语法为: lambda-introducer [&x, &y]
这里的捕获列表应该理解为:X& x, Y& y ; 因为我们实际上是取的x,y的引用而不是地址。
main() {
vector<int> v;
for
(int
i = 0; i < 10; ++i) {
v.push_back(i);
}
int
x = 1;
int
y = 1;
for_each(v.begin(), v.end(), [&x, &y](int& r) {
const int
old = r;
r *= x * y;
x = y;
y = old;
});
for_each(v.begin(), v.end(), [](int
n) { cout << n <<
" "; });
cout << endl;
cout << x <<
", " << y << endl;
}
运行结果如下 -
0 0 0 6 24 60 120 210 336 504
8, 9
上面代码会被编译器“翻译”成: