C++ 11和C++98相比有哪些新特性
C++11标准提供了许多有用的新特性。这篇文章特别针对使C++11和C++98相比看上去像一门新语言的特性,因为:
C++11改变了书写C++代码的风格和习惯,也改变了设计C++库的方式。例如,你会看到更多的被当作参数和返回值的智能指针,还有按值(byvalue)返回巨大对象的函数。
它们被使用的非常广泛,在大多数代码中你都能看到它们。举个例子,在现代C++中几乎每5行C++代码你就能看到auto关键字。
还有一些其它的非常好的C++11新特性,但先把这篇文章所描述的新特性熟悉起来把,因为这些被广泛使用的特性展示了为什么C++11代码是简洁的,安全的和快速的,就像其它现代主流开发语言一样,并且性能和传统C++一样强大。
1.Auto
在任何可能的时候使用auto。因为有两个原因。第一,非常明显,能够避免重复输入我们已经声明的并且编译器已经认识的类型名称,这是非常方便的。
//C++98 map::iteratori=m.begin(); doubleconstxlimit=config["xlimit"]; singleton&s=singleton::instance(); //C++11 autoi=begin(m); autoconstxlimit=config["xlimit"]; auto&s=singleton::instance();
第二,当有遇到一个你不知道或者无法用语言表达的类型时,auto就不仅仅是使用方便这么简单了,比如,大多数lambda函数的类型,你不能够容易的将其类型拼写出来甚至根本就不能够写出来。
//C++98 binder2ndx=bind2nd(greater(),42); //C++11 autox=[](inti){returni>42;};
注意,使用auto并没有修改代码的语义。代码仍然是静态输入(staticallytyped)的,并且每个表达式都干净利落;只是不再强制我们多余的重新声明类型的名称。
一些人开始的时候害怕使用auto,因为给人的感觉像是并没有声明(重新声明)我们想要的类型,这意味着我们可能会突然得到一个不同的类型。如果你想显示的做强制类型转换,这没有问题;声明目标类型就可以了。但是在大部分情况下,使用auto就足够了,由于出现错误而得到另外一个类型的情况很少见,在使用强静态类型(strongstatictyping)情况下,如果类型出现错误编译器就会告诉你。
2.智能指针,nodelete
总是使用智能指针,不要用原生指针和delete。除非需要实现你自己的底层数据结构(把原生指针很好的封装在类(classboundary)中
如果你知道你是另外一个对象的唯一拥有着,使用unique_ptr来表示唯一的拥有权。一个"newT"表达式能很快的初始化一个拥有这个智能指针的对象,特别是unique_ptr。典型的例子是指向实现的指针(PimplIdiom):
//C++11Pimplidiom:headerfile classwidget{ public: widget(); //...(seeGotW#100)... private: classimpl; unique_ptrpimpl; }; //implementationfile classwidget::impl{/*...*/}; widget::widget():pimpl{newimpl{/*...*/}}{} //...
使用shared_ptr来表示共享所有权(sharedownership)。使用make_shared来创建共享对象更好。
//C++98 widget*pw=newwidget(); ::: deletepw; //C++11 autopw=make_shared();
使用weak_ptr来打破循环和表示可选性(比如实现一个对象缓存)
//C++11 classgadget; classwidget{ private: shared_ptrg;//ifsharedownership }; classgadget{ private: weak_ptr w; };
如果你了解到另外一个对象比你的生存周期要长,并且你想观察这个对象,那么使用原生指针(rawpointer)。
//C++11
classnode{
vector
node*parent;
public:
:::
};
3.Nullptr
用nullptr来表示一个空指针,不要再使用数字0或者宏NULL来表示空指针了,因为这些是模棱两可的,既能表示整形也可表示指针。
//C++98
int*p=0;
//C++11
int*p=nullptr;
4.Rangefor
对一个范围内的元素进行有序访问,基于range的for循环会是更方便的用法。
//C++98
for(vector
total+=*i;
}
//C++11
for(autod:v){
total+=d;
}
5.非成员begin和end
使用非成员函数begin(x)和end(x)(不是x.begin()和x.end()),因为begin(x)和end(x)是可扩展的,能同所有容器类型一块工作——甚至数组也可以——并不是只针对提供了STL风格的x.begin()和x.end()成员函数的容器。
如果你正在使用一个非STL集合类型,这个类型提供迭代器但不是STL风格的x.begin()和x.end(),你可以对他的非成员函数begin()和end()进行重载,这样你就可以使用同STL容器同样的风格进行编码。标准中举了一个例子:数组,并且提供了对象的begin和end函数:
vector
inta[100];
//C++98
sort(v.begin(),v.end());
sort(&a[0],&a[0]+sizeof(a)/sizeof(a[0]));
//C++11
sort(begin(v),end(v));
sort(begin(a),end(a));
6.Lambda函数和算法
Lambda表达式改变了游戏规则,它会时不时的改变你的编码方式,这种方式优雅并且快速。Lambda使现存STL算法实用性提高了百倍。 //C++98:writeanakedloop(usingstd::find_ifisimpracticallydifficult) 想使用一个循环或者类似的语言特性(languagefeature)但实际上在该语言中并不存在,怎么办?将其实现成模板函数(库算法)就可以了,多亏了lambda,使用它就像是用一个语言特性一样的方便,但是更灵活,因为它确实是一个库而不是一个固定的语言特性。 //C# 熟悉一下lambda吧,你会发现他们很有用,并不只是在c++中,它们已经在几个主流语言中得到支持并且流行开来。 7.Move/&& 把move当作是对拷贝的优化最合适不过了,虽然它也包含其他方面的东西(像完美转发(perfectforwarding)) //C++98:alternativestoavoidcopying 8.统一初始化和初始化列表 没有发生变化的:当初始化一个non-POD或者auto的本地变量时,继续使用熟悉的不带额外花括号{}的=语法。 //C++98orC++11 在其他情况中(特别是随处可见的使用()来构造对象),使用花括号{}会更好。使用花括号{}能避免一些潜在的问题:你不会突然得到一个收缩转换(narrowingconversions)后的值(比如,float转换成int),也不会有偶尔突发的未初始化POD成员变量或者数组的存在,也能避免在c++98中会碰到的奇怪事:你的代码编译没问题,你需要的是变量但实际上你声明了一个函数,这都源于C++声明语法的模糊不清,ScottMeyers的著名说法:“C++最令人苦恼的解析”。通过使用新风格的语法上面解析问题会不复存在。 //C++98 //C++98 最后,有时候传递不带(type-namedtemporary)的函数参数是很方便的: voiddraw_rect(rectangle);
新增加的C++库的设计都以支持lambad表达式为前提(例如:PPL),甚至有一些库需要通过你编写lambda表达式来使用库(例如:c++AMP)。
这里有个例子,找到v中的>X并且
vector
for(;i!=v.end();++i){
if(*i>x&&*i
//C++11:usestd::find_if
autoi=find_if(begin(v),end(v),[=](inti){returni>x&&i
lock(mut_x){
...usex...
}
//C++11withoutlambdas:alreadynice,andmoreflexible(e.g.,canusetimeouts,otheroptions)
{
lock_guard
...usex...
}
//C++11withlambdas,andahelperalgorithm:C#syntaxinC++
//Algorithm:template
lock(mut_x,[&]{
...usex...
});
move语义改变了我们设计API的方式。我们会越来越多的将函数设计成returnbyvalue。
vector
:::
vector
voidmake_big_vector(vector
:::
vector
make_big_vector(result);
//C++11:move
vector
:::
autoresult=make_big_vector();//guaranteednottocopythevector
如果你想获得比copy更高效的办法,对你的类型使用move语义吧。
inta=42; //stillfine,asalways
//C++11
autox=begin(v);//nonarrowingornon-initializationispossible
rectangle w(origin(),extents()); //oops,declaresafunction,iforiginandextentsaretypes
complex
int a[]={1,2,3,4};
vector
for(inti=1;i<=4;++i)v.push_back(i);
//C++11
rectangle w {origin(),extents()};
complex
int a[]{1,2,3,4};
vector
新的{}语法在几乎任何地方都能出色的工作。
X::X(/*...*/):mem1(init1),mem2(init2,init3){/*...*/}
//C++11
X::X(/*...*/):mem1{init1},mem2{init2,init3}{/*...*/}
//C++98
draw_rect(rectangle(myobj.origin,selection.extents));
//C++11
draw_rect({myobj.origin,selection.extents});
我不喜欢使用花括号{}的唯一地方是在初始化一个非POD变量的时候,像autox=begin(v);使用花括号会使代码不必要的丑陋,因为我知道了它是一个类类型,所以我不必担心收缩转换,并且现代编译器已经对额外的拷贝(或者额外move,如果类型是move-enabled)进行了优化。