Visual Studio 2010作为下一代开发工具,当然不会错过对新的C++标准C++0x的支持。除了随着之前发布的Visual C++ Feature Pack而引入的TR1包含的部分特性外,在新的Visual Studio 2010中,还引入了4个重要的C++新特性,即:Lambdas, auto, 右值引用 和 static_assert。这些新特性的引入,必将给C++注入新的活力。
Visual Studio 2010的C++0x特性
Lambda表达式使得函数可以在使用的地方定义,并且可以在Lambda函数中使用Lambda函数之外的数据。这就为针对集合操作带来了很大的便利。在作用上,Lambda表达式类似于函数指针和函数对象,Lambda表达式很好地兼顾了函数指针和函数对象的优点,却没有它们的缺点。相对于函数指针或是函数对象复杂的语法形式,Lambda表达式使用非常简单的语法就可以实现同样的功能,降低了Lambda表达式的学习难度,避免了使用复杂的函数对象或是函数指针所带来的错误。我们可以看一个实际的Visual Studio 2010的C++0x特性例子:
- view plaincopy to clipboardprint?
- #include "stdafx.h"
- #include <algorithm>
- #include <iostream>
- #include <ostream>
- #include <vector>
- using namespace std;
- int _tmain(int argc, _TCHAR* argv[])
- {
- 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;
- }
这段代码循环遍历输出vector中的每一个数,并判断这个数是奇数还是偶数。我们可以随时修改Lambda表达式而改变这个匿名函数的实现,修改对集合的操作。在这段代码中,C++使用一对中括号“[]”来表示Lambda表达式的开始,其后的”(int n)”表示Lambda表达式的参数。这些参数将在Lambda表达式中使用到。
静态断言static_assert
在之前的C++标准C++03中,我们可以使用两种断言:
使用预处理中的条件编译和#error指令,可以在预处理阶段检查一些编译条件
可以使用宏assert来进行运行时检查,以确保程序逻辑的正确性
但使用条件编译并不能对模板参数进行检查,因为模板实例化是在编译期进行的,而#error方法是在预处理阶段进行的。assert宏则是在运行时进行检查。不难发现,我们缺少了一样东西,那就是可用于在编译期对模板进行检查的工具。于是,静态断言应运而生。
在新的C++标准C++0x中,加入了对静态断言的支持,引入了新的关键字static_assert来表示静态断言。使用静态断言,我们可以在程序的编译时期检测一些条件是否成立,这个特性在调试模板函数的模板参数时特别有用。在编译的时候,模板函数实例化,这时我们就可以使用静态断言去测试模板函数的参数是否按照我们的设计拥有合适的值。例如下面这段代码:
- view plaincopy to clipboardprint?
- template <int N> struct Kitten {
- static_assert(N < 2, "Kitten
requires N < 2." );- };
- int main() {
- Kitten<1> peppermint;
- Kitten<3> jazz;
- return 0;
- }
当我们在主函数中使用“1”去实例化Kitten这个结构体时,在编译的时候,静态断言static_assert会测试参数N的值,当N的值小于2时就会产生一个断言错误,并将相应的调试帮助信息输出到“Error List”窗口中,这样程序员就可以对问题快速定位,解决问题就更加方便了。
另外,静态断言还带来很多其他的优势。例如静态断言在编译时进行处理,不会产生任何运行时刻空间和时间上的开销,这就使得它比assert宏具有更好的效率。另外比较重要的一个特性是如果断言失败,它会产生有意义且充分的诊断信息,帮助程序员快速解决问题
auto关键字
在C++0x中,auto关键字的意义发生了改变: 它可以“从初始化器(initialize)中推导出所代表的变量的真正类型”。这种对auto关键字的使用方式可以大大消除当前冗长和易出错的代码。我们看一个实际的例子:
在这段代码中,我们使用auto关键字来代替了真正的数据类型map
另外,跟其他数据类型一样,我们也可以对auto关键字进行修饰,例如添加const,指针(*),左值引用(&),右值引用(&&)等等,编译器会根据auto类型所代表的真正的数据来决定这些修饰的具体含义。
为了兼容一些旧有的C++代码,我们可以使用/Zc:auto这个编译器选项,来告诉编译器是采用auto关键字的原有定义还是在新标准C++0x中的定义。
右值引用
作为最重要的一项语言特性,右值引用(rvalue references)被引入到 C++0x中。我们可以通过操作符“&&”来声明一个右值引用,原先在C++中使用“&”操作符声明的引用现在被称为左值引用。
- view plaincopy to clipboardprint?
- int a;
- int& a_lvref = a; // 左值引用
- int b;
- int&& b_rvref = b; // 右值应用
- int a;
- int& a_lvref = a; // 左值引用
- int b;
- int&& b_rvref = b; // 右值应用
左值引用和右值引用的表现行为基本一致,它们唯一的差别就是右值引用可以绑定到一个临时对象(右值)上,而左值引用不可以。例如:
- view plaincopy to clipboardprint?
- int& a_lvref = int(); // error C2440: 'initializing' : cannot convert from 'int' to 'int &'
- int&& b_rvref = int(); // OK!
- int& a_lvref = int(); // error C2440: 'initializing' : cannot convert from 'int' to 'int &'
- int&& b_rvref = int(); // OK!
在第一行代码中,我们将一个临时对象int()绑定到一个左值引用,将产生一个编译错误。而在第二行中,我们将临时对象绑定到右值引用,就可以顺利通过编译。
右值是无名的数据,例如函数的返回值一般说来就是右值。当对右值进行操作的时候,右值本身往往没有必要保留,因此在某些情况下可以直接“移动”之。通过右值引用,程序可以明确的区分出传入的参数是否为右值,从而避免了不必要的拷贝,程序的效率也就得到了提高。我们考虑一个简单的数据交换的小程序,从中来体会右值引用所带来的效率提升。我们可以写一个函数swap来实现两个变量值的交换:
- view plaincopy to clipboardprint?
- template swap(T& a, T& b)
- {
- T tmp(a); // tmp对象创建后,我们就拥有了a的两份拷贝
- a = b; // 现在我们拥有b的两份拷贝
- b = tmp; // 现在我们拥有a的两份拷贝
- }
- template swap(T& a, T& b)
- {
- T tmp(a); // tmp对象创建后,我们就拥有了a的两份拷贝
- a = b; // 现在我们拥有b的两份拷贝
- b = tmp; // 现在我们拥有a的两份拷贝
- }
在这段代码中,虽然我们只是为了进行简单的数据交换,但是却执行了多次对象拷贝。这些对象的拷贝操作,特别是当这些对象比较大的时候,无疑会影响程序的效率。
那么,如果使用右值引用如何实现呢
- view plaincopy to clipboardprint?
- // RValueRef.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
- template
- T&& move(T&& a)
- {
- return a;
- }
- template void swap(T& a, T& b)
- {
- T tmp(move(a)); // 对象a被移动到对象tmp,a被清空
- a = move(b); // 对象b被移动到对象a,b被清空
- b = move(tmp); // 对象tmp被移动到对象b
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- int a = 1;
- int b = 2;
- swap(a, b);
- return 0;
- }
- // RValueRef.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
- template
- T&& move(T&& a)
- {
- return a;
- }
- template void swap(T& a, T& b)
- {
- T tmp(move(a)); // 对象a被移动到对象tmp,a被清空
- a = move(b); // 对象b被移动到对象a,b被清空
- b = move(tmp); // 对象tmp被移动到对象b
- }
- int _tmain(int argc, _TCHAR* argv[])
- {
- int a = 1;
- int b = 2;
- swap(a, b);
- return 0;
- }
在这段重新实现的代码中,我们使用了一个move()函数来代替对象的赋值操作符“=”,move()只是简单地接受一个右值引用或者左值引用作为参数,然后直接返回相应对象的右值引用。这一过程不会产生拷贝(Copy)操作,而只会将源对象移动(Move)到目标对象。
正是拷贝(Copy)和移动(Move)的差别,使得右值引用成为C++0x中最激动人心的新特性之一。从实践角度讲,它能够完美是解决C++中长久以来为人所诟病的临时对象的效率问题。从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷。从库设计者的角度讲,它给库设计者又带来了一把利器。而对于广大的库使用者而言,不动一兵一卒便能够获得“免费的”效率提升。
在Visual Studio 2010中,因为有了Visual Studio 2010的C++0x特性的支持,重新点燃了程序员们对C++的热情。C++重振雄风,指日可待!
【编辑推荐】