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
http://www.cnblogs.com/haippy/archive/2013/05/31/3111560.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'; }