c++11标准带来的最显著变化(及为何该引起你的注意)

来源:互联网 发布:犀牛软件破解版下载 编辑:程序博客网 时间:2024/06/07 22:47

来源 :http://article.yeeyan.org/view/234235/250515


简介:自C++语言首次标准化以来,13年的时间过去了。前C++标准委员会成员, Danny Kalev,阐述该编程语言有何改进,及其它如何有助于写出更好代码。

Bjarne Stroustrup,c++首创人,最近表示:c++11“感觉象一种新语言——各部分彼此更协调了”。确实,核心c++11已发生显著的变化。它现在支持lambda表达式,对象的自动类型推定,统一的初始化语法,委托构造函数,函数声明显式指定可删除及显式指定缺省,空指针关键字,以及最重要的右值引用——一种特性:兆示着在如何预想和处置对象上的范式发生了转化。以上只是举例。

c++11标准库也获得修订,加入了新算法,新容器类,原子操作,type traits,正则表达式,新型灵巧指针,异步方法,当然还有多线程库。

新核心与库功能的完整清单点此跳转。

标准在1998年通过之后,两位会议委员曾预言下一代c++标准必将具备某种内置的废物回收器(缩写为GC),而且可能不会支持多线程,因为在可移植线程模型的定义上,所涉及的技术复杂度不确定。13年后,新的标准c++11接近完成。意外的是,GC缺席了,反倒具有了一种顶尖的线程库。

在本文当中,我试图阐述标准中最显著的变化,以及这其中究竟是怎么一回事。你会看到,线程库并非其唯一的变化。新标准基于专家数十载的努力,令c++更其妥贴可用。就如Rogers Cadenhead指出的那样:“如同迪斯科、Pet Rocks(译者注:某种过时的美国游戏),或是类如大龄奥运游泳选手一般,c++已经老气横秋,而新出标准相当地令人惊喜”。

首先,来看看有些什么突出的语言特性。

Lambda表达式 用Lambda表达式可实现在调用地点定义局部函数,这么一来,显著避免了因启用函数对象而面临的繁琐表达和安全上的风险。一条lambda表达式有如下形式:

[capture](parameters)->return-type {body}

在一个函数调用的参数表内出现的[]方括弧构造,以之标示出一个lambda表达式的存在。我们举例来看。

假定要对字符串容器内的大写字母进行计数。利用标准库函数for_each遍历字符组,而如下列出的lambda式子来测定每个字母是否为大写。每个大写字母,一旦找出,则lambda式子把变量Uppercase加一,该变量定义在lambda表达式之外。

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<<" uppercaseletters in: "<< s<<endl;< div="">

}

这个表达式看似如同置身于函数调用体内部的一种函数定义代码。用&符号引导的Uppercase表示lambda表达式需要获取一个对变量Uppercase的引用作为实参,藉此,便可以修改该变量。如果不要&符号,Uppercase则作值传递。c++11的lambda包含也着针对成员函数的构造设计。

自动类型推定和delcltype(译者注:类型宣告) 在标准c++03下,对象的声明里必须指定对象的类型。然而多种情形下,对象的声明含有一个初始化要素。标准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

在对象类型太冗长或是(模板情形下)自动生成时,自动类型推定在简化表达上起主导作用。

 void func(const vector &vi)

{

 vector::const_iterator ci=vi.begin();

}

象这样声明迭代子:

 auto ci=vi.begin();

关键字auto 不是新货色;它可上溯至前ANSI C时代。而今,C++11修改了它的含义;auto不再意味着设定对象为自动存贮类型了。而是用于声明一个对象,且该对象的类型由对象的初始化表达式推定出来。

C++11提供一种类似机制来捕获对象或表达式的类型。新进操作符decltype接受一个表达式,“返回”该表达式的类型:

const vector vi;

typedef decltype (vi.begin()) CIT; 

CIT another_const_iterator;

统一的初始化语法 c++至少具有四种不同的初始化表示法,部分是重叠的。

带括弧的初始化看起来象这样:

std::string s("hello");

int m=int(); //default initialization

也可以用 = 记号 来达成某些情形下的初始化目的:

std::string s="hello";

int x=5;

对于POD(Plain Old Data的缩写,这种类型具有C兼容特点)性质的集合体,可用大括弧:

 int arr[4]={0,1,2,3};

struct tm today={0};

最后一种情况,构造函数则使用成员初始化(列表):

struct S {

 int x;

 S(): x(0) {}

 };

存在初始化的多种变体是富含困扰的根源,不仅对生手来说是麻烦,高手也如此。更糟的是,c++03标准之下,给POD数组分配堆要使用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 arrayinitializer

 };

针对容器来说,现在可以淘汰充斥着push_back方法的冗长代码了(译者注:有时会使用循环结构)。C++11允许直观地表示容器初始化:

 // C++11 container initializer

vector 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;”记号部分指示编译器生成函数的缺省实作体。缺省的函数有两处优势:其一,比手工实作出来的代码更有效率,其二,把程序员从手工定义那些函数所引发的琐细事务中解脱出来。

与缺省函数对应,也存在指定删除函数:

 int func()=delete;

声明删除的函数在防止复制对象的方面尤其有用。C++类自动声明一个复制构造函数和一个赋值操作。要禁止这种默认行为,使用记号=delete:

 struct NoCopy{

 NoCopy & operator =( const NoCopy & ) =delete;

 NoCopy ( const NoCopy & ) = delete;

 };

 NoCopy a;

 NoCopy b(a); //compilation error, copy ctor isdeleted

nullptr C++终于提供了一个关键字用于表示空指针常量。nullptr替代了有隐患的宏NULL和文字量0, 它们一直以来被当作空指针代用品,已行之有年。现在,nullptr具备了强类型的性质(译者注:c++的宗旨之一是作为一种强类型语言,参考《C++语言的设计与演化》):

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;<div="">

int (A::*pmf)()=nullptr; //pointer to member function

void (*pmf)()=nullptr; //pointer to function

委托构造函数

构造函数可能要调用本类中的其它构造函数:

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<<"delegatingctor"<<="" span="">

2号构造函数为委托者,由它调用目标:即1号构造函数。

右值引用在 C++03中,引用类型只能与左值绑定。 C++11引入了新的引用类型称为右值引用。右值引用能与右值相绑定,例如临时对象和文字量。

 增加右值引用的首要因素在于体现移动语义。与传统的复制不同,移动是指目的对象占据源对象的资源为己有,而源对象失去对资源的隶属关系。在某些情形中,复制对象的开销太大且没有必要,移动操作是其合理的替代。为感受移动语义在效率上好处,我们来看一个字串交换的过程。试看如下精简后的代码:

 void naiveswap(string &a, string & b)

{

 string temp = a;

 a=b;

 b=temp;

 }

这种开销是不合理的。复制整个串将引发堆分配,然后从源对象复制数据至目的地。相比而言,移动串只是交换数据成员(译者注:是地址),而不必引起内存分配,复制字符组和删除内存这一系列的动作开销:

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);

}

假定实现一个支持移动的类,可以这么声明移动构造函数和移动赋值操作符:

class Movable

{

Movable (Movable&&); //move constructor

Movable&& operator=(Movable&&); //moveassignment operator

};

C++11标准库广泛使用移动语义。许多算法和容器目前都享有移动语义带来的优化。

2003 年,以库技术报告1(TR1)的形式,使得C++经历了一场重大改观。 TR1含有新容器类型族unordered_set,unordered_map,unordered_multiset, unordered_multimap),以及数个新进功能:正则表达式, tuples(译者注:元组收纳器),函数对象封装器等等。伴随标准 C++11的出台,TR1也正式集成到C++标准之内,这意味着TR1一直以来所具备的全新库功能已经可用。这里列举一部分C++11的库功能:

线程库 毫无疑问,以编程员的角度看,最重大的改进在于并发主题。 C++11引入了线程类来表示执行线程,promises 和 futures(译者注:并发编程设计领域的术语,参考wiki)——指在并发环境中用于同步的对象,针对启动并发任务而设计的async()函数模板,及针对声明线程专有数据(thread-unique data )的存储类型thread_local。欲对c++11线程库进行一次快餐体验,请阅读 Anthony Williams撰写的《c++0x,更简明的多线程》(Simpler Multithreading in c++0x)。

新灵巧指针类  C++98只定义了一种灵巧指针类,auto_ptr,现在已过时。C++11 具备一些全新的灵巧指针类:shared_ptr,及最近才加入的unique_ptr。两者与其它标准库组件兼容,故此在标准容器内能够安全地保存这些灵巧指针,也能使用标准算法操作这些对象。

新算法C++11标准库定义了新的算法,即模拟集合论领域的操作符all_of(),any_of(),none_of()。下列代码清单里,对[first,first+n]所限定的范围内,使用谓词方法ispositive()遍历检查每个元素,并且采用all_of(),any_of(),none_of()方法,来测定该范围整体的性质。

#include

//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负责创建有限的递增值序列,通过向*first表示的第一子项指定初始值,那么该值则被施加前缀++操作,以生成后序数据。

include

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应用接口,sockets,图形用户界面,reflection——当然,还有合理的自动化废物回收器。新标准确实提供了大量新功能,使得C++更形可靠,高效(是滴,这是迄今为止最高效的!参见谷歌的benchmarktests),易于学习和使用。

是否这些C++11 下的变化似乎有点动静太那个猛啦,要淡定。费点时间,循序渐进地领会这些新变化。最终,你说不定会赞同 Stroustrup的表述: C++11的确感觉象种新语言——更好的那种。

</pc<<endl;<>

</endl;<>

 

原创粉丝点击