作为C++下一代标准的C++0x,其命名的本意无非是“200x年正式推出的C++新标准”,但目前(2010年)显然已没有可能,似乎改名为C++1x才名正言顺,不过为了避免不必要的混乱,C++标准委员会仍然坚持使用原名。
关于lambda表达式
在函数型语言(FP)中大行其道的lambda表达式被引入以“多范式”(multi-paradigm)为标签的C++语言应该是一件顺理成章的事。lambda表达式的引入对于改善C++语言中以STL算法为代表的FP编程范式可谓是大有裨益,事实上它也由此成为C++0x中最受欢迎的新特性之一。
在新标准中,lambda表达式将被编译器翻译成C++程序员早已熟悉的函数对象(function object),并替代后者行使“STL算法的参数”这一功能。下面用代码说明这一点:
代码1
代码说明
- 代码1用连续的整数0-19来填充长度为20的数组a,最后打印用以填充数组的变量n来确认数组的长度。
- array容器是C++0x中新引入标准的STL容器,其功能相当于原生数组+STL容器规范。
array<int, 20> a; 可以被大致看作 int a[20]; - auto关键字在C++0x中被赋予新的职责:类型自动推导。
在 auto i = a.begin(); 这句语句中,变量 i 的类型与其初始化表达式 a.begin() 相同,即array<int, 20>::iterator
若不使用auto关键字,这句语句将不得不写成冗长的 array<int, 20>::iterator i = a.begin();
代码2
代码说明
- 代码2改用FP编程范式来完成同样的功能,具体来说它使用了STL库的generate算法来填充数组a。
- 代码2中的generate算法用全局函数 next_int 来充当它的生成器。
由于作为生成器的全局函数 next_int 不能接受额外的参数,代码1中局部变量 n 的功能不得已改用全局变量 gn 来完成。 - 相比较代码1,
代码2的优点在于:用generate算法明确了代码的意图,提高了代码可读性。
其缺点在于:算法的可变部分(即生成器代码全局函数 next_int )与算法调用表达式相距过远,另外所使用的全局函数 next_int 以及全局变量 gn 也污染了全局命名空间。
注:若局部函数(local function)被引入C++0x(正在讨论中),这两个问题即可得到解决。
代码3
代码说明
- 代码3同样使用generate算法来填充数组a,所不同的是它用函数对象“generator类的实例”来充当算法的生成器。
- 鉴于函数对象类 generator 可以保存并改变状态,代码1中局部变量 n 的功能改由 generator 类的成员变量 n_ 来完成。
- 相比较代码2,
代码3的优点在于:不必使用全局变量来保存状态,提高了代码的可维护性。
但其缺点也很明显:算法可变部分(即生成器代码generator 类)不仅过于冗长,而且与算法调用表达式分离的问题也没有得到解决。
注:由于C++0x中函数对象已经可以在函数内声明,代码3中的generator类也可以作为局部类在main中声明,如此便解决了全局命名空间被污染的问题。
代码4
代码说明
- 代码4同样使用generate算法来填充数组a,不过这一次算法的生成器来源于Boost.Lambda类库。
- 代码4中Lambda类库中的var函数用于捕获本地变量n并生成函数对象,运算符++被重载以生成更大的函数对象。
简单来讲,Lambda类库就是一个函数对象的生成器。 - 代码4克服了代码2,3的缺点:算法可变部分(即生成器代码var(n)++)不仅简短而且就处在算法调用表达式之内。
不过代码4也产生了新的问题:
使用者必须学习并熟悉Lambda类库,由于这个类库包含了大量新的概念及用法,学习曲线较为陡峭。
Lambda类库使用了较为高级的表达式模板技术,受C++语言本身的限制,有时所生成的lambda表达式并不太直观,降低了可读性。
另外出于同样的原因,大量使用Lambda类库的代码还存在编译速度慢、一旦用错出错信息难以辨读等缺点。
代码5
代码说明
- 代码5同样使用generate算法来填充数组a,不过这一次算法的生成器来源于本文的主题:作为C++0x标准新特性的lambda表达式。
- 代码5中 [&]{return n++;} 部分就是运用了新特性的lambda表达式。
如果加上语法中可省略的部分,该lambda表达式应为:[&]()->int{return n++;} 。
(即将第12行替换成第13行,并反注释) - [&]为lambda表达式的导入符(introducer),&的意思为引用捕获所有局部变量,即将lambda表达式中用到的所有局部变量的引用传入lambda表达式中。
- 由于此处lambda表达式的函数体 {return n++;} 中只用到局部变量n,所以也可以将导入符 [&] 改成 [&n] 表示只捕获局部变量 n 的引用。
(即将第12行替换成第14行) - 与引用捕获相对应的是值捕获,即将lambda表达式中用到的所有局部变量的值拷贝并传入lambda表达式中。
若采用值捕获,导入符[&]须改为[=],局部变量n的值将被拷贝进入lambda表达式中,数组a的内容不受影响,但程序最后输出为0。
(即将第12行替换成第15行)
注意:值捕获而拷贝进入lambda表达式中的局部变量缺省情况下不可修改,若要修改须使用mutable关键字。 - 由于此处lambda表达式的函数体 {return n++;} 中只用到局部变量n,所以也可以将导入符 [=] 改成 [n] 表示只捕获局部变量 n 的值。
(即将第12行替换成第16行) - 如果lambda表达式无需捕获任何局部变量,导入符可用 [] ,参见第17行。
第17行代码(反注释后)调用for_each算法将所有数组成员的值乘以2。 - lambda表达式的导入符之后为参数列表部分,这部分与普通函数的参数列表大致相同。
注意:lambda表达式参数列表部分的参数不可省略变量名,也不允许有缺省值。
第12行的generate算法中lambda表达式没有参数,所以这部分是 () 。
注意:空参数列表在返回值类型等中间部分省略的情况下也可省略。
第17行的for_each算法中需要将数组成员的引用传给lambda表达式,所以这部分是 (int& v) 。 - lambda表达式的中间部分包括mutable关键字、异常规范、返回值类型三部分内容。
- lambda表达式的返回值类型采用C++0x标准新引入的后置语法,即 ->T ,某些情况下可省略。
- lambda表达式的函数体与普通函数一样放在最后,在以下两种情况下返回值类型可省略:
函数体只包括一句语句:return expression; 此时函数返回值类型与expression相同,参见第12行(省略返回值类型)和第13行(不省略返回值类型)。
函数体不返回任何值,即返回值类型为void,参见第17行(省略返回值类型)和第18行(不省略返回值类型)。 - 与代码4相比,代码5不仅保持了算法可变部分(即生成器代码[&]{return n++;})短小精悍以及与算法调用不分离等优点,而且在很大程度上还克服了其难于学习、不够直观以及编译速度慢的缺点。
由此不难得出结论:使用了新特性lambda表达式的代码5就是这五种方案中的最佳选择。 - 代码5中的lambda表达式经编译器处理后将被翻译成类似于代码3中的函数对象类及其调用,
但在实际效果上lambda表达式可以被看作C/C++语言中尚不具备的局部函数(local function)。
注:真正的local function能否进入C++0x尚在讨论之中,目前不被看好。
补记
代码2,3,5部分摘自
(已作修改)。
相关链接
C++0x wiki
http://en.wikipedia.org/wiki/C%2B%2B0x