C++11的重大改变

C++11的重大改变

此篇文章是本人翻译自前C++标准委员会的Danny Kalev写的The Biggest Changes in C++11 (and Why You Should Care)

原文地址:

The Biggest Changes in C++11 (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利用了这一点,IT夜班车允许在定义变量的时候不指定类型。

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个好处,

  1. 比手动添加的函数效率要高
  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_multisetunordered_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完全像一个新的语言-一个变的更好的语言”。

 

 

 

版权所有,禁止转载. 如需转载,请先征得博主的同意,并且表明文章出处,否则
按侵权处理.

    分享到:
This entry was posted in C/C++ and tagged . Bookmark the permalink.

3 Responses to C++11的重大改变

  1. Pingback: C++11的线程类 | 360converter博客360converter博客

  2. Pingback: C++11的新特性之shared_ptr和unique_ptr | 360converter博客360converter博客

  3. Pingback: std::thread错误 terminate called without an active exception | 360converter博客360converter博客

Leave a Reply

Your email address will not be published. Required fields are marked *

*