掀起C++ 11的神秘面纱

C++之父Bjame Stroustrup最近说C++11就像一个新语言,的确,C++11核心已经发生了巨大的变化,它现在支持Lambda表达式,对象类型自动推断,统一的初始化语法,委托构造函数,deleted和defaulted函数声明nullptr,以及最重要的右值引用。
首页 新闻资讯 行业资讯 掀起C++ 11的神秘面纱

【51CTO 6月21日外电头条】C++之父Bjame Stroustrup最近说C++11就像一个新语言,的确,C++11核心已经发生了巨大的变化,它现在支持Lambda表达式,对象类型自动推断,统一的初始化语法,委托构造函数,deleted和defaulted函数声明nullptr,以及最重要的右值引用。

 [[33955]]

【编辑推荐】

C++0x FAQ中文版(http://imcc.blogbus.com/logs/106046323.html

C++11标准库也使用新的算法,新的容器类,原子操作,类型特征,正则表达式,新的智能指针,async()函数和多线程库进行了改造。

C++11的新内核和库特性完整列表请移步这里(http://www2.research.att.com/~bs/C++0xFAQ.html) C++标准在1998年获得通过后,有两位委员会委员预言,下一代C++标准将“肯定”包括内置的垃圾回收器(GC),但可能不会支持多线程,因为定义一个可移植的线程模型涉及到的技术太复杂了,13年后,新的C++标准C++11也接近完成,你猜怎么着?让那两位委员没想到的是,本次更新还是没有包括GC,但却包括了一个先进的线程库。

在这篇文章中,我将介绍C++11标准中发生的***变化,以及为什么应该引起注意,正如你将看到的,线程库不是唯一的变化,新标准采纳了数十位专家的意见,使C++变得更有意义。正如Rogers Cadenhead指出的那样,它们就像迪斯科、宠物石和长胸毛的奥运游泳选手一样不可思议。

首先,让我们看看C++11核心语言的一些突出特性。

Lambda表达式

Lambda表达式允许你在本地定义函数,即在调用的地方定义,从而消除函数对象产生的许多安全风险,Lambda表达式的格式如下:

复制

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

[]里是函数调用的参数列表,表示一个Lambda表达式的开始,让我们来看一个Lambda例子:

假设你想计算某个字符串包含多少个大写字母,使用for_each()遍历一个char数组,下面的Lambda表达式确定每个字母是否是大写字母,每当它发现一个大写字母,Lambda表达式给Uppercase加1,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<<" uppercase letters in: "<< s<<endl;
   }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

这是因为,如果你定义的函数主体被放置在另一个函数调用内部,[&Uppercase]中的“&”记号意味着Lambda主体获得一个Uppercase的引用,以便它能修改,如果没有这个特殊记号,Uppercase将通过值传递,C++11 Lambda表达式也包括成员函数构造器。

自动类型推断和decltype

在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
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

相反,你可以声明下面这样的迭代器:

复制

void fucn(const vector<int> &vi)
   {
   vector<int>::const_iterator ci=vi.begin();
   }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

关键字auto不是什么新生事物,我们早已认识,它实际上可以追溯到前ANSI C时代,但是,C++11改变了它的含义,auto不再指定自动存储类型对象,相反,它声明的对象类型是根据初始化代码推断而来的,C++11删除了auto关键字的旧有含义以避免混淆,C++11提供了一个类似的机制捕捉对象或表达式的类型,新的操作符decltype需要一个表达式,并返回它的类型。

复制

const vector<int> vi;
   typedef decltype (vi.begin()) CIT;
   CIT another_const_iterator;
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

统一初始化语法

C++至少有4个不同的初始化符号,有些存在重叠,括号初始化语法如下:

复制

std::string s("hello");
   int m=int(); //default initialization
  • 1.

  • 2.

  • 3.

  • 4.

在某些情况下,你也可以使用“=”符号进行初始化:

复制

std::string s="hello";
   int x=5;
  • 1.

  • 2.

  • 3.

  • 4.

对于POD聚合,你还可以使用大括号:

复制

int arr[4]={0,1,2,3};
   struct tm today={0};
  • 1.

  • 2.

  • 3.

  • 4.

***,构造函数使用成员进行初始化:

复制

struct S {
    int x;
    S(): x(0) {} };
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

显然,这么多种初始化方法会引起混乱,对新手来说就更痛苦了,更糟糕的是,在C++03中,你不能初始化POD数组成员,POD数组使用new[]分配,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
   };
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

关于容器,你可以和一长串的push_back()调用说再见了,在C++11中,你可以直观地初始化容器:

复制

// C++11 container initializer
   vector vs<string>={ "first", "second", "third"};
   map singers =
     { {"Lady Gaga", "+1 (212) 555-7890"},
       {"Beyonce Knowles", "+1 (212) 555-0987"}};
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

类似地,C++11支持类似的数据成员类内初始化:

复制

class C
   {
    int a=7; //C++11 only
   public:
    C();
   };
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

#p#

Deleted和Defaulted函数

一个表单中的函数:

复制

struct A
   {
    A()=default; //C++11
    virtual ~A()=default; //C++11
   };
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

被称为一个defaulted函数,“=default;”告诉编译器为函数生成默认的实现。Defaulted函数有两个好处:比手工实现更高效,让程序员摆脱了手工定义这些函数的苦差事。

与defaulted函数相反的是deleted函数:

复制

int func()=delete;
  • 1.

Deleted函数对防止对象复制很有用,回想一下C++自动为类声明一个副本构造函数和一个赋值操作符,要禁用复制,声明这两个特殊的成员函数=delete即可:

复制

struct NoCopy
  {
      NoCopy & operator =( const NoCopy & ) = delete;
      NoCopy ( const NoCopy & ) = delete;
  };
  NoCopy a;
  NoCopy b(a); //compilation error, copy ctor is deleted
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

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
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

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
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

委托构造函数

在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"<
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

构造函数#2,委托构造函数,调用目标构造函数#1。

右值引用

C++03中的引用类型只能绑定左值,C++11引入了一种新型引用类型,叫做右值引用,右值引用可以绑定左值,例如,临时对象和字面量。增加右值引用的主要原因是move(移动)语义,它和传统的复制不一样,移动意味着目标对象偷窃了源对象的资源,留下一个状态为“空”的资源,在某些情况下,复制一个对象代价既高又没有必要,可以用一个移动操作代替,如果你想评估移动带来的性能收益,可以考虑字符串交换,一个幼稚的实现如下:

复制

void naiveswap(string &a, string & b)
  {
   string temp = a;
   a=b;
   b=temp;
  }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

像这样写代价是很高的,复制字符串必须分配原始内存,将字符从源位置复制到目标位置,相反,移动字符串仅仅是交换两个数据成员,不用分配内存,复制char数组和删除内存:

复制


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

}  

  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

  • 34.

  • 35.

  • 36.

  • 37.

  • 38.

  • 39.

  • 40.

  • 41.

  • 42.

  • 43.

  • 44.

  • 45.

  • 46.

  • 47.

  • 48.

  • 49.

  • 50.

  • 51.

  • 52.

  • 53.

  • 54.

  • 55.

  • 56.

  • 57.

  • 58.

  • 59.

  • 60.

  • 61.

  • 62.

  • 63.

  • 64.

  • 65.

  • 66.

  • 67.

  • 68.

如果你实现的类支持移动,你可以像下面这样声明一个移动构造函数和一个移动赋值操作符:

复制

class Movable
 {
 Movable (Movable&&); //move constructor Movable&& operator=(Movable&&); //move assignment operator };
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

#p#

C++11标准库广泛使用了移动语义,许多算法和容器现在都为移动做了优化。

C++11标准库

C++于2003年以库技术报告1(TR1)的形式经历了重大改版,TR1包括新的容器类(unordered_set,unordered_map,unordered_multiset和unordered_multimap)和多个支撑正则表达式、元组和函数对象封装器等的新库。随着C++11标准获得通过,TR1和自它***发布以来新增的库被正式纳入标准的C++标准,下面是C++11标准库的一些特性:

线程库

站在程序员的角度来看,C++11最重要的新功能毫无疑问是并行操作,C++11拥有一个代表执行线程的线程类,在并行环境中用于同步,async()函数模板启动并行任务,为线程独特的数据声明thread_local存储类型。如果你想找C++11线程库的快速教程,请阅读Anthony William的“C++0x中更简单的多线程”。

新的智能指针类

C++98只定义了一个智能指针类auto_ptr,它现在已经被废弃了,C++11引入了新的智能指针类shared_ptr和最近添加的unique_ptr,两者都兼容其它标准库组件,因此你可以在标准容器内安全保存这些智能指针,并使用标准算法操作它们。

新的算法

C++11标准库定义了新的算法模仿all_of(),any_of()和none_of()操作,下面列出适用于ispositive()到(first, first+n)范围,且使用all_of(), any_of() and none_of() 检查范围的属性的谓词:

复制

#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
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

一种新型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);
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

算法iota()创建了一个值顺序递增的范围,好像分配一个初始值给*first,然后使用前缀++使值递增,在下面的代码中,iota()分配连续值{10,11,12,13,14}给数组arr,并将{‘a’,’b’,’c’}分配给char数组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'}
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

C++11仍然缺乏一些有用的库,如XML API,套接字,GUI,反射以及前面提到的一个合适的自动垃圾回收器,但C++11的确也带来了许多新特性,让C++变得更加安全,高效,易学易用。

如果C++11的变化对你来说太大的话,也不要惊慌,多花些时间逐渐消化这一切,当你完全吸收了C++11的变化后,你可能就会同意Stroustrup的说法:C++11感觉就像一个新语言,一个更好的新语言。

原文出处:http://www.softwarequalityconnection.com/2011/06/the-biggest-changes-in-c11-and-why-you-should-care/

原文名:The Biggest Changes in C++11

【编辑推荐】

  1. 一个实用主义者的观点:如何学习使用C++

  2. 程序员新招:Java与C++混合编程

14    2011-06-22 09:43:01    C++