VC10和C++ 0x (1) - lambda表达式
2009-09-05 02:02
302 查看
【本文大部分内容译自VisualC++TeamBlog】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++编译器厂商也终于在VisualStudio2010中开始支持C++0x的特性。
Lambda表达式,auto和静态断言(static_assert)
VisualStudio2010中的VisualC++编译器,即VC10,包含了4个C++0x的语言特性-lambda表达式,auto,static_assert和rvaluereference(右值引用).
相关链接:
C++0xlanguagefeaturestatus:http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2705.html
C++0xlibraryfeaturestatus:http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2706.html
C++0xWorkingDraft:http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2798.pdf
lambdas
使用过函数式编程语言(如lisp,F#)或一些动态语言(如Python,Javascript)的大侠对于lambda表达式一定不会陌生。
在C++0x中,引入了lambda表达式来定义无名仿函数。下面是一个lambda表达式的简单例子:
C:\Temp>cl/EHsc/nologo/W4meow.cpp>NUL&&meow
0123456789
for_each一行中,中括号[]称为lambdaintroducer,它告诉编译器接下来的是一个lambda表达式;接下来(intn)是lambda表达式的参数声明;最后大括号里边就是“函数体”了。
注意这里因为lambda表达式生成的是functor,所以“函数体”实际上是指这个functor的operator()的调用部分。你也许会问:那么返回值呢?缺省情况下lambda表达式生成的functor调用
返回类型为void.
所以,可以理解为上边的代码会被编译器翻译成如下:
下面的例子中lambda表达式的“函数体”包含多条语句:
#include<algorithm>
#include<deque>
#include<iostream>
#include<iterator>
#include<ostream>
#include<vector>
usingnamespacestd;
intmain(){
vector<int>v;
for(inti=0;i<10;++i){
v.push_back(i);
}
deque<int>d;
transform(v.begin(),v.end(),front_inserter(d),[](intn){returnn*n*n;});
for_each(d.begin(),d.end(),[](intn){cout<<n<<"";});
cout<<endl;
}
上例中返回值n*n*n很简单,类型推演是显而易见的。但是如果lambda表达式中有非常复杂的表达式时,编译器可以无法推演出其类型,或者是推演出现二义性,这时候你可以
显式地指明返回值类型。如下所示:
黑体部分中有的“->double”显式地指明了lambda表达式的返回类型是double.
以上例子中的lambda都是无状态的(stateless),不包含任何数据成员。很多时候我们需要lambda包含数据成员以保存状态,这一点可以通过“捕获”(capturing)局部变量来实现。
lambda表达式的导入符(lambda-introducer)是空的,也就是“[]”,表明该lambda是一个无状态的。但是在lambda导入符中可以指定一个“捕获列表”(capture-list)。
上边的代码中的lambda使用了局部变量x和y,将值介于x和y之间的元素从集合中删除。
程序运行示例如下-
Input:47
01234789
上边的代码可以理解为:
上面代码中很重要的一点信息是: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)。
当编译器在lambda的作用范围内看到局部变量x,y时,它会以传值的方式从main函数中将他们捕获。
下面我们来看如何突破前面提到的3点限制。
第一点,修改lambda表达式中的局部变量拷贝(e.g.m_a,m_b)
缺省情况下,lambda的operator()是const修饰的,但是你可以使用mutable关键字改变这一点。
代码运行结果如下
00062460120210336504
1,1
这里我们解决了第一个限制,但是却产生了一个新的限制
4.lambda中对捕获变量的修改并不会影响到main函数中的局部变量,因为lambda捕获局部变量使用的是传值方式
下面该“传引用”的方式登场了,它能够有效地解决2,3,4三个限制。
传引用的语法为:lambda-introducer[&x,&y]
这里的捕获列表应该理解为:X&x,Y&y;因为我们实际上是取的x,y的引用而不是地址。
运行结果如下-
00062460120210336504
8,9
上面代码会被编译器“翻译”成:
注意:当你使用lambda时,VC10编译器会为lambda的定义部分自动禁用C4512警告。
当以传引用方式捕获局部变量时,lambda的函数对象在自己内部以引用方式保存main函数中的局部变量。
当然因为使用的是局部对象的引用,使用lambda表达式时一定要注意不能够超出局部变量的生命周期。
和上文提高的[=]类似,我们可以用[&]来以“传引用”的方式捕获所有的局部变量。
到目前为止,局部变量的捕获方式要么是“值语义”要么是“引用语义”,那么可以混合这两种方式吗?可以!
例如:[a,b,c,&d,e,&f,g],其中变量d和f是按引用语义捕获,而a,b,c,e和g是按值语义捕获。
另外很有用的一点是:你可以指定一个缺省捕获(capture-default),然后重载(override)某些局部变量的捕获方式。
下边例子中[=,&sum,&product]告诉编译器用值语义方式捕获所有的局部变量,但是有两个例外-sum和product是按引用语义来捕获。
运行结果如下-
00062460120210336504
sum:45,product:362880
x:1,y:1
再来看看下边的代码-在lambda中使用类成员变量
不幸的是,编译这段代码将产生这样的错误:
errorC3480:'Kitty::m_toys':alambdacapturevariablemustbefromanenclosingfunctionscope
为什么呢?lambda表达式能够让你不活局部变量,但是类的数据成员并不是局部变量。
解决方案呢?别着急。lambda为捕获类的数据成员大开方便之门,你可以捕获this指针。
运行结果-
Ifyougaveme0toys,Iwouldhave5toystotal.
Ifyougaveme1toys,Iwouldhave6toystotal.
Ifyougaveme2toys,Iwouldhave7toystotal.
当lambda表达式捕获“this”时,编译器看到m_toys后会在this所指向对象的范围内进行名字查找,m_toys被隐式地推演为this->m_toys。当然你也可以让编译器省省力气。显式地在
捕获列表中使用this->m_toys。
lambda比较智能,你也可以隐式地捕获this指针。如下所示:
运行结果:
Ifyougaveme0toys,Iwouldhave5toystotal.
Ifyougaveme1toys,Iwouldhave6toystotal.
Ifyougaveme2toys,Iwouldhave7toystotal.
注意你也可以在上面代码中用[&],但是结果是一样的-this指针永远是按值语义被传递(捕获)的。你也不能够使用[&this],呵呵。
如果你的lambda表达式是没有参数的,那么lambda表达式的导入符后边的括号()也可以省掉。例如:
运行结果如下:
0123456789
i:10
上边是[&](){returni++;}的简写形式。个人认为省掉括号并不是什么好的codingstyle。
下面是纯粹搞笑的写法:
注意lambda的语法为:
(lambda-parameter-declaration-listopt)mutableoptexception-specificationoptlambda-return-type-clauseopt
所以如果你需要用到mutable或者指定lambda的返回类型,空的括号就不能够省略了。
最后尽然lambda表达式生成是普通的函数对象,所以函数对象支持的用法lambda都支持。例如和tr1的function一起使用,
看看下边的代码,是不是很酷?
运行结果:
0123456789
0149162536496481
0182764125216343512729
【THEEND】
尽管C++社区对C++0x很是追捧,但是各厂商对于新标准的支持并不热乎。盼星星盼月亮,微软作为Windows平台上最强势的C++编译器厂商也终于在VisualStudio2010中开始支持C++0x的特性。
Lambda表达式,auto和静态断言(static_assert)
VisualStudio2010中的VisualC++编译器,即VC10,包含了4个C++0x的语言特性-lambda表达式,auto,static_assert和rvaluereference(右值引用).
相关链接:
C++0xlanguagefeaturestatus:
C++0xlibraryfeaturestatus:
C++0xWorkingDraft:
lambdas
使用过函数式编程语言(如lisp,F#)或一些动态语言(如Python,Javascript)的大侠对于lambda表达式一定不会陌生。
在C++0x中,引入了lambda表达式来定义无名仿函数。下面是一个lambda表达式的简单例子:
//File:meow.cpp #include<algorithm> #include<iostream> #include<ostream> #include<vector> usingnamespacestd; intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } for_each(v.begin(),v.end(),[](intn){cout<<n<<"";}); cout<<endl; return0; }
C:\Temp>cl/EHsc/nologo/W4meow.cpp>NUL&&meow
0123456789
for_each一行中,中括号[]称为lambdaintroducer,它告诉编译器接下来的是一个lambda表达式;接下来(intn)是lambda表达式的参数声明;最后大括号里边就是“函数体”了。
注意这里因为lambda表达式生成的是functor,所以“函数体”实际上是指这个functor的operator()的调用部分。你也许会问:那么返回值呢?缺省情况下lambda表达式生成的functor调用
返回类型为void.
所以,可以理解为上边的代码会被编译器翻译成如下:
#include<algorithm> #include<iostream> #include<ostream> #include<vector> usingnamespacestd; structLambdaFunctor{ voidoperator()(intn)const{ cout<<n<<""; } }; intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } for_each(v.begin(),v.end(),LambdaFunctor()); cout<<endl; return0; }
为了方便,以下会用"lambda返回void"的简短表述来代替冗长啰嗦的表述:lambda表达式生成一个functor类型,这个functor类型的函数调用操作符(operator())返回的类型是void. 请大家一定记住:lambda表达式生成了类型,并构造该类型的实例。
下面的例子中lambda表达式的“函数体”包含多条语句:
#include<algorithm> #include<iostream> #include<ostream> #include<vector> usingnamespacestd; intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } for_each(v.begin(),v.end(),[](intn){ cout<<n; if(n%2==0){ cout<<"even"; }else{ cout<<"odd"; } }); cout<<endl; return0; }
上文提到了lambda表达式缺省情况下返回void.那么如果需要返回其他类型呢? 答案是:lambda表达式的“函数体”中如果有一个return的表达式,例如{returnexpression;},那么编译器将自动推演expression的类型作为返回类型。
#include<algorithm>
#include<deque>
#include<iostream>
#include<iterator>
#include<ostream>
#include<vector>
usingnamespacestd;
intmain(){
vector<int>v;
for(inti=0;i<10;++i){
v.push_back(i);
}
deque<int>d;
transform(v.begin(),v.end(),front_inserter(d),[](intn){returnn*n*n;});
for_each(d.begin(),d.end(),[](intn){cout<<n<<"";});
cout<<endl;
}
上例中返回值n*n*n很简单,类型推演是显而易见的。但是如果lambda表达式中有非常复杂的表达式时,编译器可以无法推演出其类型,或者是推演出现二义性,这时候你可以
显式地指明返回值类型。如下所示:
transform(v.begin(),v.end(),front_inserter(d),[](intn)->double{ if(n%2==0){ returnn*n*n; }else{ returnn/2.0; } });
黑体部分中有的“->double”显式地指明了lambda表达式的返回类型是double.
以上例子中的lambda都是无状态的(stateless),不包含任何数据成员。很多时候我们需要lambda包含数据成员以保存状态,这一点可以通过“捕获”(capturing)局部变量来实现。
lambda表达式的导入符(lambda-introducer)是空的,也就是“[]”,表明该lambda是一个无状态的。但是在lambda导入符中可以指定一个“捕获列表”(capture-list)。
intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } intx=0; inty=0; cout<<"Input:"; cin>>x>>y; v.erase(remove_if(v.begin(),v.end(),[x,y](intn){returnx<n&&n<y;}),v.end()); for_each(v.begin(),v.end(),[](intn){cout<<n<<"";}); cout<<endl; }
上边的代码中的lambda使用了局部变量x和y,将值介于x和y之间的元素从集合中删除。
程序运行示例如下-
Input:47
01234789
上边的代码可以理解为:
classLambdaFunctor{ public: LambdaFunctor(inta,intb):m_a(a),m_b(b){} booloperator()(intn)const{returnm_a<n&&n<m_b;} private: intm_a; intm_b; }; intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } intx=0; inty=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)。
intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } intx=0; inty=0; cout<<"Input:"; cin>>x>>y;//EVIL! v.erase(remove_if(v.begin(),v.end(),[=](intn){returnx<n&&n<y;}),v.end()); for_each(v.begin(),v.end(),[](intn){cout<<n<<"";}); cout<<endl; }
当编译器在lambda的作用范围内看到局部变量x,y时,它会以传值的方式从main函数中将他们捕获。
下面我们来看如何突破前面提到的3点限制。
第一点,修改lambda表达式中的局部变量拷贝(e.g.m_a,m_b)
缺省情况下,lambda的operator()是const修饰的,但是你可以使用mutable关键字改变这一点。
intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } intx=1; inty=1; for_each(v.begin(),v.end(),[=](int&r)mutable{ constintold=r; r*=x*y; x=y; y=old; }); for_each(v.begin(),v.end(),[](intn){cout<<n<<"";}); cout<<endl; cout<<x<<","<<y<<endl; }
代码运行结果如下
00062460120210336504
1,1
这里我们解决了第一个限制,但是却产生了一个新的限制
4.lambda中对捕获变量的修改并不会影响到main函数中的局部变量,因为lambda捕获局部变量使用的是传值方式
下面该“传引用”的方式登场了,它能够有效地解决2,3,4三个限制。
传引用的语法为:lambda-introducer[&x,&y]
这里的捕获列表应该理解为:X&x,Y&y;因为我们实际上是取的x,y的引用而不是地址。
intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } intx=1; inty=1; for_each(v.begin(),v.end(),[&x,&y](int&r){ constintold=r; r*=x*y; x=y; y=old; }); for_each(v.begin(),v.end(),[](intn){cout<<n<<"";}); cout<<endl; cout<<x<<","<<y<<endl; }
运行结果如下-
00062460120210336504
8,9
上面代码会被编译器“翻译”成:
#pragmawarning(push) #pragmawarning(disable:4512)//assignmentoperatorcouldnotbegenerated classLambdaFunctor{ public: LambdaFunctor(int&a,int&b):m_a(a),m_b(b){} voidoperator()(int&r)const{ constintold=r; r*=m_a*m_b; m_a=m_b; m_b=old; } private: int&m_a; int&m_b; }; #pragmawarning(pop) intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } intx=1; inty=1; for_each(v.begin(),v.end(),LambdaFunctor(x,y)); copy(v.begin(),v.end(),ostream_iterator<int>(cout,"")); cout<<endl; cout<<x<<","<<y<<endl; }
注意:当你使用lambda时,VC10编译器会为lambda的定义部分自动禁用C4512警告。
当以传引用方式捕获局部变量时,lambda的函数对象在自己内部以引用方式保存main函数中的局部变量。
当然因为使用的是局部对象的引用,使用lambda表达式时一定要注意不能够超出局部变量的生命周期。
和上文提高的[=]类似,我们可以用[&]来以“传引用”的方式捕获所有的局部变量。
到目前为止,局部变量的捕获方式要么是“值语义”要么是“引用语义”,那么可以混合这两种方式吗?可以!
例如:[a,b,c,&d,e,&f,g],其中变量d和f是按引用语义捕获,而a,b,c,e和g是按值语义捕获。
另外很有用的一点是:你可以指定一个缺省捕获(capture-default),然后重载(override)某些局部变量的捕获方式。
下边例子中[=,&sum,&product]告诉编译器用值语义方式捕获所有的局部变量,但是有两个例外-sum和product是按引用语义来捕获。
intmain(){ vector<int>v; for(inti=0;i<10;++i){ v.push_back(i); } intsum=0; intproduct=1; intx=1; inty=1; for_each(v.begin(),v.end(),[=,&sum,&product](int&r)mutable{ sum+=r; if(r!=0){ product*=r; } constintold=r; r*=x*y; x=y; y=old; }); for_each(v.begin(),v.end(),[](intn){cout<<n<<"";}); cout<<endl; cout<<"sum:"<<sum<<",product:"<<product<<endl; cout<<"x:"<<x<<",y:"<<y<<endl; }
运行结果如下-
00062460120210336504
sum:45,product:362880
x:1,y:1
再来看看下边的代码-在lambda中使用类成员变量
classKitty{ public: explicitKitty(inttoys):m_toys(toys){} voidmeow(constvector<int>&v)const{ for_each(v.begin(),v.end(),[m_toys](intn){ cout<<"Ifyougaveme"<<n<<"toys,Iwouldhave"<<n+m_toys<<"toystotal."<<endl; }); } private: intm_toys; }; intmain(){ vector<int>v; for(inti=0;i<3;++i){ v.push_back(i); } Kittyk(5); k.meow(v); }
不幸的是,编译这段代码将产生这样的错误:
errorC3480:'Kitty::m_toys':alambdacapturevariablemustbefromanenclosingfunctionscope
为什么呢?lambda表达式能够让你不活局部变量,但是类的数据成员并不是局部变量。
解决方案呢?别着急。lambda为捕获类的数据成员大开方便之门,你可以捕获this指针。
classKitty{ public: explicitKitty(inttoys):m_toys(toys){} voidmeow(constvector<int>&v)const{ for_each(v.begin(),v.end(),[this](intn){ cout<<"Ifyougaveme"<<n<<"toys,Iwouldhave"<<n+m_toys<<"toystotal."<<endl; }); } private: intm_toys; }; intmain(){ vector<int>v; for(inti=0;i<3;++i){ v.push_back(i); } Kittyk(5); k.meow(v); }
运行结果-
Ifyougaveme0toys,Iwouldhave5toystotal.
Ifyougaveme1toys,Iwouldhave6toystotal.
Ifyougaveme2toys,Iwouldhave7toystotal.
当lambda表达式捕获“this”时,编译器看到m_toys后会在this所指向对象的范围内进行名字查找,m_toys被隐式地推演为this->m_toys。当然你也可以让编译器省省力气。显式地在
捕获列表中使用this->m_toys。
lambda比较智能,你也可以隐式地捕获this指针。如下所示:
classKitty{ public: explicitKitty(inttoys):m_toys(toys){} voidmeow(constvector<int>&v)const{ for_each(v.begin(),v.end(),[=](intn){ cout<<"Ifyougaveme"<<n<<"toys,Iwouldhave"<<n+m_toys<<"toystotal."<<endl; }); } private: intm_toys; }; intmain(){ vector<int>v; for(inti=0;i<3;++i){ v.push_back(i); } Kittyk(5); k.meow(v); }
运行结果:
Ifyougaveme0toys,Iwouldhave5toystotal.
Ifyougaveme1toys,Iwouldhave6toystotal.
Ifyougaveme2toys,Iwouldhave7toystotal.
注意你也可以在上面代码中用[&],但是结果是一样的-this指针永远是按值语义被传递(捕获)的。你也不能够使用[&this],呵呵。
如果你的lambda表达式是没有参数的,那么lambda表达式的导入符后边的括号()也可以省掉。例如:
intmain(){ vector<int>v; inti=0; generate_n(back_inserter(v),10,[&]{returni++;}); for_each(v.begin(),v.end(),[](intn){cout<<n<<"";}); cout<<endl; cout<<"i:"<<i<<endl; }
0123456789
i:10
上边是[&](){returni++;}的简写形式。个人认为省掉括号并不是什么好的codingstyle。
下面是纯粹搞笑的写法:
intmain(){
[](){}();
[]{}();
}
注意lambda的语法为:
(lambda-parameter-declaration-listopt)mutableoptexception-specificationoptlambda-return-type-clauseopt
所以如果你需要用到mutable或者指定lambda的返回类型,空的括号就不能够省略了。
最后尽然lambda表达式生成是普通的函数对象,所以函数对象支持的用法lambda都支持。例如和tr1的function一起使用,
看看下边的代码,是不是很酷?
usingnamespacestd;
usingnamespacestd::tr1;
voidmeow(constvector<int>&v,constfunction<void(int)>&f){
for_each(v.begin(),v.end(),f);
cout<<endl;
}
intmain(){
vector<int>v;
for(inti=0;i<10;++i){
v.push_back(i);
}
meow(v,[](intn){cout<<n<<"";});
meow(v,[](intn){cout<<n*n<<"";});
function<void(int)>g=[](intn){cout<<n*n*n<<"";};
meow(v,g);
}
运行结果:
0123456789
0149162536496481
0182764125216343512729
【THEEND】
相关文章推荐
- 【转】VC10和C++ 0x (1) - lambda表达式
- VC10和C++ 0x (1) - lambda表达式
- VC10和C++ 0x (3) - static_assert
- VC10和C++ 0x (1) - lambda表达式
- VC10和C++ 0x (3) - static_assert
- VC/C/C++ 代码10
- C++ 0x新特性:详细讲解lambda表达式
- C++ 0x中的Lambda表达式
- win7-64,vs2010带的vc10环境下用dos编译C++的步骤
- C++构造函数、拷贝构造函数、赋值运算符重载 调用时机 GCC与VC在对象作为返回值的不同处理
- 如何用 C++ 在 10 行内写出八皇后?
- C++ AMP: 在C++中Lambda表达式
- VC、C++保存二叉树在文件中然后读出来
- VS2010点滴——C++的Lambda表达式
- 《C++ Primer Plus(第六版)》(10)(第八章 函数探幽 笔记)
- 设计模式C++实现(10)——桥接模式
- windows配置openvc3.2.0,java版,python版,C++visual stdio2017版
- C++ 0x 之 Lambda:贤妻与娇娃,你娶谁当老婆?听 FP 如何点化 C++
- c和c++的一些训练题(10)(打印螺旋方阵)
- 在vc2008 mfcC++中使用sqlite的示例