C++11的重大改变
此篇文章是本人翻译自前C++标准委员会的Danny Kalev写的The Biggest Changes in C++11 (and Why You Should Care)
原文地址:
http://blog.smartbear.com/c-plus-plus/the-biggest-changes-in-c11-and-why-you-should-care/
自C++的第一个迭代版本算起,已经有11年头了,前C++标准委员会的成员Danny Kalev解释了编程语言如何被提高,如何帮助你写更好的代码。
Bjarne Stroustrup,C++的创始人,最近说“C++11完全就像是一个新的语言,每个功能(原文是piceces)更好的被搭配在一起。的确,C++11的核心部分改变很大。支持,
- lambda表达式
- 自动类型
- 自醒
- 统一的初始化语法
- 代理构造函数(delegating constructors)
- 默认函数声明
- nullptr指针
- 右值引用
尤其是右值引用,它预示着在思考和处理对象的方式的迁移(原文是:a feature that augurs a paradigm shift in how one conceives and handles objects),这仅仅是个例子。
C++11的库也被改进了,这些改进包括,新的算法,新的容器类,元操作,类型特征,正则表达式,新的智能指针,ansyc(),当然支持多线程。
在1998年,C++标准得到批准后,2个委员会的成员预言,下一个C++的标准一定会引入垃圾回收,并且考虑到定义可移植的线程模型的复杂度,多线程可能不被支持。13后,新的C++标准,也就是C++11出来了,结果是,GC(垃圾回收)没有被引入,而包含了state-of-art(这个鬼东西不知道怎么翻译,先放在这里)的线程库。
在这篇文章中我将解释C++11的一些重大改变,并且解释为什么它们可以称为重大改变。就像你将要看到的,线程库不是唯一的改变,新标准建立在几十年的专业知识上面,它使得c++更加的正确(原文是relevant),就像Rogers Cadenhead说的那样,“那实在是太完美了,当某些东西老的想迪斯科,Pet Rocks,长了胸毛的奥运会游泳运动员。
后先让我们看看C++11基本的一些语言特性。
Lambda表达式
Lambda表达式能让你就地定义一个函数体,这样可以避免单调乏味,并且有安全隐患的函数对象。Lambda表达式的格式如下,
[capture](parameters)->return-type {body}
[capture](参数)->返回值类型 {函数体}
函数调用列表中的[]是Lambda表达式的开始标志。下面让我们看一个例子,
假设你要统计一个字符串中有多少个大写的字符,我们使用for_each来遍历字符数组,然后用lambda表达式来检测每一个字符是不是大写的。没遇到一个大写的字符,lambda表达式就将外部定义好的个变量Upperbase的值加一。
int main()
{
char s[]="Hello World!";
int Uppercase = 0; //modified by the lambda
for_each(s, s+sizeof(s), [&Uppercase] (char c) {
if (isupper(c))
Uppercase++;
});
cout<< Uppercase<<" uppercase letters in: "<< s<<endl;
}
这样使用,感觉的就像是在一个函数调用的地方,定义了一个函数体,这里的[&Uppercase]表示Lambda表示获取这个变量的引用,然后需要在内部改变的它的值,如果没有’&’符号,那么Uppercase就是传值进去。C++11的Lambda表达式也包括成员函数的构造(原文,constructs for member function )
自醒和delctype
在C++03里,定义变量的时候必须指定类型(原文中用的是object,我觉得有问题,因为在c++中,并不是所有的变量都是一个类的实例,有基本的类型,比如int, char),然而在很多情况下,变量的声明包括初始化过程。C++11利用了这一点,
允许在定义变量的时候不指定类型。auto x=0; //x has type int because 0 is int auto c='a'; //char auto d=0.5; //double auto national_debt=14400000000000LL;//long long
当一个变量的类型很繁杂(原文,verbose)或者一个变量是自动生成,比如模板类,自醒就很有用,考虑如下场景,
void func(const vector<int> &vi) { vector<int>::const_iterator ci=vi.begin(); }
利用C++11的自动变量,可以改为
auto ci=vi.begin();
关键字auto本身并不是新东西,其实在前标准C的时代就有了。不过在c++11里面,它的含义被改变了,auto不再是只限于有自动存储类型的对象,而是用在可以功过初始化过程推论出它的类型的变量定义前面。为了避免混淆,旧的autu的含义已经从c++11中移除。
C++11还提供了同样的机制来获取变量或者表达式的类型。它就是decltype,这个新的操作符接受一个表达式,返回它的类型。
const vector<int> vi; typedef decltype (vi.begin()) CIT; CIT another_const_iterator;
一致的初始化语法
C++至少有4种不同初始化表达方法,有些之间是有重叠的,
圆括号的初始化方式
std::string s("hello"); int m=int(); //default initialization
在一些情况下,你可以使用=来达到同样的效果
std::string s="hello"; int x=5;
POD聚合情况下,还可以使用大括号(译者注:POD是Plain Old Data的缩写,其实全称应该是Plain Old Data Type, 旧的数据类型,关于POD的介绍请参看
http://en.cppreference.com/w/cpp/types/is_pod)
int arr[4]={0,1,2,3}; struct tm today={0};
最后,构造函数使用成员初始化,
struct S { int x; S(): x(0) {} };
这个变化不仅仅在初学者种造成困扰。更糟糕的是,在03版的C++,你不可以初始化使用new[]分配内存的POD成员。C++11使用一致的大括号表达式解决了这个困扰
class C { int a; int b; public: C(int i, int j); }; C c {0,0}; //C++11 only. Equivalent to: C c(0,0); int* a = new int[3] { 1, 2, 0 }; /C++11 only class X { int a[4]; public: X() : a{1,2,3,4} {} //C++11, member array initializer };
再使用容器类型时,你可以告别那一长串的push_back的调用说再见了,在C++11中你可以非常直观的初始化容器类,
// C++11 container initializer vector<string> vs={ "first", "second", "third"}; map singers = { {"Lady Gaga", "+1 (212) 555-7890"}, {"Beyonce Knowles", "+1 (212) 555-0987"}};
类似的,C++11支持类的数据成员的内部初始化,
class C { int a=7; //C++11 only public: C(); };
删除的和默认的函数
具有如下形式的函数称为默认函数
struct A { A()=default; //C++11 virtual ~A()=default; //C++11 };
上面的=default 告诉编译器编译的时候为这个函数生成默认的函数实现。默认的函数有2个好处,
- 比手动添加的函数效率要高
- 把程序员从这些机械的函数定义中解放出来
和默认函数相反的是删除函数,
int func()=delete;
删除函数在防止对象拷贝方面很有用。让我们回想一下,C++自动生成拷贝构造函数和赋值操作符函数。为了防止拷贝,我们将这2个函数声明为删除函数。
struct NoCopy { NoCopy & operator =( const NoCopy & ) = delete; NoCopy ( const NoCopy & ) = delete; }; NoCopy a; NoCopy b(a); //compilation error, copy ctor is deleted
nullptr
终于,C++有了专门用来定义空指针的常量,nullptr替代了诟病已久的NULL宏,已经被用了很多年的当做空指针用的数字0。nullptr是强类型的,
void f(int); //#1 void f(char *);//#2 //C++03 f(0); //which f is called? //C++11 f(nullptr) //unambiguous, calls #2
nullptr适用于所有的指针类型,包括函数指针和指向成员的指针。
const char *pc=str.c_str(); //data pointers if (pc!=nullptr) cout<<pc<<endl; int (A::*pmf)()=nullptr; //pointer to member function void (*pmf)()=nullptr; //pointer to function
代理构造函数
在C++11中构造函数可以调用同一类中的另外一个构造函数,例如
class M //C++11 delegating constructors { int x, y; char *p; public: M(int v) : x(v), y(0), p(new char [MAX]) {} //#1 target M(): M(0) {cout<<"delegating ctor"<<endl;} //#2 delegating };
在2号代理构造函数调用了目标构造函数1号
右值引用
在03版的C++中,只可以左值引用,C++11中引入了新的引用类型-右值引用。右值引用可以绑定到一个右值上面,例如,临时对象和常量。
引入右值引用的主要原因是 move语法。不像传统的拷贝,move表示的是目标对象“偷“(译者注:作者使用的是pilfers)源对象的资源,导致源变成了一种空的状态。有些情况下,拷贝构造是非常昂贵和不必要的,此时就可以使用move来替代。为了体会move操作带来的性能提升,可以想想字符串的交换。一种原始的实现,
void naiveswap(string &a, string & b) { string temp = a; a=b; b=temp; }
上面的操作很昂贵,因为这会有分配一次内存和拷贝字符的过程,而move操作,则刚好相反,只是交换2个数据成员。没有分配内存,拷贝字符和释放内存的操作。
void moveswapstr(string& empty, string & filled) { //pseudo code, but you get the idea size_t sz=empty.size(); const char *p= empty.data(); //move filled's resources to empty empty.setsize(filled.size()); empty.setdata(filled.data()); //filled becomes empty filled.setsize(sz); filled.setdata(p); }
如果你想实现一个支持move的类,可以这样做,
class Movable { Movable (Movable&&); //move constructor Movable&& operator=(Movable&&); //move assignment operator };
C++11的标准库中使用很多的move操作,很多的算法和容器现在都使用move操作优化过的。
C++11标准库
C++在2003年,以库的技术报告(简称TR1)的方式经历了一次大的改变。TR1包含了新的容器类(unordered_set
, unordered_map
,unordered_multiset
, unordered_multimap
)还有新的正则表达式的库。在C++11中,TR1以及后来被添加的库被引入到C++标准当中,下面介绍一些C++11标准库的特性。
线程库
从程序员的角度看,并发毫无疑问是一个最重要的改变,C++11引入了线程类,promises和futures是用来做线程同步的对象,async函数模板用来启动一个并发任务,thread_local存储类型用来表示线程本地变量。要快速的浏览C++11的线程库,请参看Anthony Williams写的更为简单的C++0x多线程。
新的智能指针类
98版的C++只定义了一个智能指针类 auto_ptr,现在auto_ptr已经被放弃了,C++11引入了新的智能指针类shared_ptr,和最新添加的unique_ptr。这2个智能指针类和其他的标准模板库组件互相兼容,因此你可以将它们存储在标准容器中,使用标准算法操作它们,而不用考虑它们的安全问题。
新的C++算法
C++11实现了关于集合理论操作如,allof(), anyof(), noneof(), 下面的代码演示了,如何将预测函数 ispositive()应用在[first, first+n]这个范围,并且使用allof(), anyof(), noneof()检验这个范围的属性。
#include <algorithm> //C++11 code //are all of the elements positive? all_of(first, first+n, ispositive()); //false //is there at least one positive element? any_of(first, first+n, ispositive());//true // are none of the elements positive? none_of(first, first+n, ispositive()); //false
还有copy_n,例如使用copy_n, 拷贝数组中的5个元素到另外数组变得非常简单,
#include int source[5]={0,12,34,50,80}; int target[5]; //copy 5 elements from source to target copy_n(source,5,target);
iota以递增的方式在一个范围创建一组数列,就好像给定一个初始值,然后在这个初始值上面不断加一。在下面的例子中,itoa把连续的数列{10,11,12,13,14}赋给数组arr,{‘a’, ‘b’, ‘c’} 给c
include <numeric> int a[5]={0}; char c[3]={0}; iota(a, a+5, 10); //changes a to {10,11,12,13,14} iota(c, c+3, 'a'); //{'a','b','c'}
C++11仍然缺少一些有用库,例如操作XML的API,sockets,GUI,反射。当然还有一个较为适合的垃圾回收器。但是,它已经提供了很多特性,使得C++更加的安全,高效(是的,远比以前高效,请参看Google的性能跑分),更加容易学习和使用。
如果C++11带来的改变对你来说太大,不要怕,慢慢地理解消化它。等你理解了消化了,你也许会同意Stroustrup(译者注:人名),“C++11完全像一个新的语言-一个变的更好的语言”。
版权所有,禁止转载. 如需转载,请先征得博主的同意,并且表明文章出处,否则
按侵权处理.
3 Replies to “C++11的重大改变”