族谱网 头条 人物百科

C++11

2020-10-16
出处:族谱网
作者:阿族小谱
浏览:269
转发:0
评论:0
设计原则C++的修订包含核心语言以及标准程序库。在发展新标准的每个机能上,委员会采取了几个方向:维持稳定性和与C++98,可能的话还有C之间的兼容性;尽可能不通过核心语言的扩展,而是通过标准程序库来引进新的特色;能够演进编程技术的变更优先;改进C++以帮助系统以及库设计,而不是引进只针对特别应用的新特色;增进类型安全,提供对现行不安全的技术更安全的替代方案;增进直接对硬件工作的能力与表现;提供现实世界中问题的适当解决方案;实行“zero-overhead”原则(某些功能要求的额外支持只有在该功能被使用时才能使用);使C++易于教授与学习关照初学者被认为是重要的,因为他们构成了计算机程序员的主体。也因为许多初学者不愿扩展他们对C++的知识,只限于使用他们对C++专精的部分。此外,考虑到C++被广泛的使用(包含应用领域和编程风格),即便是最有经验的程序员在面对新的编程范式时也会成为初学者。C+...

设计原则

C++的修订包含核心语言以及标准程序库。

在发展新标准的每个机能上,委员会采取了几个方向:

维持稳定性和与C++98,可能的话还有C之间的兼容性;

尽可能不通过核心语言的扩展,而是通过标准程序库来引进新的特色;

能够演进编程技术的变更优先;

改进C++以帮助系统以及库设计,而不是引进只针对特别应用的新特色;

增进类型安全,提供对现行不安全的技术更安全的替代方案;

增进直接对硬件工作的能力与表现;

提供现实世界中问题的适当解决方案;

实行“zero-overhead”原则(某些功能要求的额外支持只有在该功能被使用时才能使用);

使C++易于教授与学习

关照初学者被认为是重要的,因为他们构成了计算机程序员的主体。也因为许多初学者不愿扩展他们对C++的知识,只限于使用他们对C++专精的部分。此外,考虑到C++被广泛的使用(包含应用领域和编程风格),即便是最有经验的程序员在面对新的编程范式时也会成为初学者。

C++核心语言的扩充

C++委员会的主要作用之一是改善语言核心。核心语言将被大幅改善的领域包括多线程(或称为“多线程”)支持、泛型编程、统一的初始化,以及性能表现的加强。

在此分成4个区块来讨论核心语言的特色以及变更: 运行期表现强化、构造期表现强化、可用性强化,还有新的功能。某些特性可能会同时属于多个区块,但在此仅于其最具代表性的区块描述。

核心语言的运行期表现强化

以下的语言机能主要用来提升某些性能表现,像是内存或是速度上的表现。

右值引用和move语义

在C++03及之前的标准,临时对象(称为右值"R-values",因为它们通常位于赋值运算符右侧)无法被改变,在C中亦同(且被视为等同于const T&)。尽管如此,在某些情况下临时对象仍然可能会被改变,但这种表现也被视为是一个有用的漏洞。

C++11增加一个新的非常数引用(reference)类型,称作右值引用(R-value reference),标记为T &&。右值引用所绑定的临时对象可以在该临时对象被初始化之后做修改,这是为了允许move语义。

C++03低性能问题的之一,就是在以传值方式传递对象时隐式发生的耗时且不必要的深度拷贝。举例而言,std::vector本质上是一个C-style数组及其大小的封装,如果一个std::vector的临时对象是在函数内部或者函数回返时创建,要将其存储就只能通过生成新的std::vector并且把该临时对象所有的数据复制过去(为了讨论上的方便,这里忽略回返值优化)。然后该临时对象会被析构,其使用的内存会被释放。

在C++11,把一个vector的右值引用作为参数std::vector的"move构造函数",可以把右值参数所绑定的vector内部的指向C-style数组的指针复制给新的vector,然后把该指针置null。由于临时变量不会被再次使用,所以不会有代码去访问该null指针;又因为该指针为null,当该临时对象超出作用域时曾经指向的内部C-style数组所使用的内存不会被释放。因此,该操作不仅无形中免去了深拷贝的开销,而且还很安全。

右值引用作为数据类型的引入,使得函数可以重载区分它的参数是值类型、传统的左值引用还是右值引用。这让除了标准库的现有代码无须任何改动就能等到性能提升。一个回返std::vector的函数的回返类型无须为了调用move构造函数而显式修改为std::vector&&,因为临时对象自动作为右值。(但是,如果std::vector是没有move构造函数的C++03版,由于传统的左值引用也可以绑定到临时对象上,因此具有const std::vector&参数的复制构造函数会被调用,导致一次显著的内存分配。)

出于安全的考虑,推行了一些限制。具名的变量被认定为左值,即使它是被声明为右值引用数据类型;为了获得右值必须使用显式类型转换,如模板函数std::move()。右值引用所绑定的对象应该只在特定情境下被修改,主要用于move构造函数中。

boolis_r_value(int&&){returntrue;}boolis_r_value(constint&){returnfalse;}voidtest(int&&i){is_r_value(i);// i為具名變數,即使被宣告成右值引用类型,i作为实参表达式也不會被認定是右值表达式。is_r_value(std::move(i));// 使用std::move()取得右值。}

由于右值引用的语义特性以及对于左值引用(L-value references;regular references)的某些语义修正,右值引用让开发者能够提供函数参数的完美转发(perfect function forwarding)。当与不定长参数模板结合,这项能力允许函数模板能够完美地转送参数给其他接受这些特定参数的函数。最大的用处是转送构造函数参数,创造出能够自动为这些特定参数调用正确构造函数的工厂函数(factory function)。这个用法可以在C++标准库中的emplace_back方法中看到。

泛化的常数表示式

C++本来就已具备常数表示式(constant expression)的概念。像是3+4总是会产生相同的结果并且没有任何的副作用。常数表示式对编译器来说是最优化的机会,编译器时常在编译期运行它们并且将值存入程序中。同样地,在许多场合下,C++标准要求使用常数表示式。例如在数组大小的定义上,以及枚举值(enumerator values)都要求必须是常数表示式。

然而,常数表示式不能含有函数调用或是对象构造函数。所以像是以下的例子是不合法的:

intGetFive(){return5;}intsome_value[GetFive()+5];// 欲產生10個整數的陣列。不合法的C++寫法

这在C++03中是不合法的,因为GetFive() + 5并不是常数表示式。C++03编译器无从得知GetFive实际上在运行期是常数。理论上而言,这个函数可能会影响全域参数,或者调用其他的非运行期(non-runtime)常数函数等。

C++11引进关键字constexpr允许用户保证函数或是对象构造函数是编译期常数。以上的例子可以被写成像是下面这样:

constexprintGetFive(){return5;}intsome_value[GetFive()+5];// 欲產生10個整數的陣列。合法的C++11寫法

这使得编译器能够了解并去验证GetFive是个编译期常数。

用constexpr修饰函数将限制函数的行为。首先,该函数的回返值类型不能为void。第二,函数的内容必须依照"return expr"的形式。第三,在参数替换后,expr必须是个常数表示式。这些常数表示式只能够调用其他被定义为constexpr的函数,或是其他常数表示式的数据参数。最后,有着这样修饰符的函数直到在该编译单元内被定义之前是不能够被调用的。

声明为constexpr的函数也可以像其他函数一样用于常量表达式以外的地方,此时不需要满足后两点。

C++11之前,可以在常量表达式中使用的的变量必须被声明为const,用常量表达式来初始化,并且必须是整型或枚举类型。C++11去除了变量必须是整型或枚举类型的限制,只要变量使用了constexpr关键字来定义:

constexprdoubleearth_gravitational_acceleration=9.8;constexprdoublemoon_gravitational_acceleration=earth_gravitational_acceleration/6.0;

这些变量都是隐式常量,必须使用常量表达式来初始化。

为了让用户自定义类型(user-defined type)参与构造常量表示式,构造函数也可以用constexpr来声明。与constexpr函数一样,constexpr构造函数必须在该编译单元内使用之前被定义。它的函数体必须为空。它必须用常量表示式初始化他的成员(member)。而这种类型的析构函数应当是平凡的(trivial)。

拥有constexpr构造函数的类型的复制构造函数通常也应该被定义为constexpr,以便该类型的对象以值传递的方式从constexpr函数回返。该类别的任何成员函数,像是复制构造函数、运算符重载函数等等,只要他们匹配常数表达式函数的定义,都可以被声明成constexpr。使得编译器能够在编译期进行类别的复制、对他们施行运算等等。

常数表达式函数或构造函数,可以以非常数表示式(non-constexpr)作为参数调用。就如同constexpr整数字面值能够指派给non-constexpr参数,constexpr函数也可以接受non-constexpr参数,其结果存储于non-constexpr参数。constexpr关键字只有当表示式的成员都是constexpr,才允许编译期常数性的可能。

对POD定义的修正

在C++03中,一个类(class)或结构(struct)要想被作为POD,必须遵守几条规则。匹配这种定义的类型能够产生与C兼容的对象内存布局(object layout),而且可以被静态初始化。但C++03标准严格限制了何种类型与C兼容或可以被静态初始化的,尽管并不存在技术原因导致编译器无法处理。如果创建一个C++03 POD类型,然后为其添加一个非虚成员函数,这个类型就不再是POD类型了,从而无法被静态初始化,也不再与C兼容,尽管其内存布局并没有发生变化。

C++11通过把POD概念划分成两个概念:平凡的(trivial)和标准布局(standard-layout),放宽了关于POD的定义。

一个平凡的类型可以被静态初始化,同时意味着使用memcpy来复制数据是合法的,而无须使用复制构造函数。平凡的类型对象的生命周期开始于其存储空间被分配时,而不是其构造函数完成时。

一个平凡的的类别或结构匹配以下定义:

平凡的默认构造函数。这可以使用默认构造函数语法,例如SomeConstructor() = default;

平凡的复制构造函数和move构造函数,可使用默认语法(default syntax)

平凡的赋值运算符和move赋值操作符,可使用默认语法(default syntax)

平凡的析构函数,不可以是虚函数(virtual)

只有在类没有虚基类和虚成员函数时,构造函数才是平凡的。复制构造函数和赋值操作符还额外要求所有非静态数据成员都是平凡的。

一个匹配标准布局的类封装成员的方式与C兼容。一个标准布局(standard-layout)的类别或结构匹配以下定义:

只有非静态的(non-static)数据成员,且这些成员也是匹配标准布局的类型

对所有non-static成员有相同的访问控制(public,private,protected)

没有虚函数

没有虚拟基类

只有匹配标准布局的基类

没有和第一个定义的non-static成员相同类型的基类

若非没有带有non-static成员的基类,就是最底层(继承最末位)的类别没有non-static数据成员而且至多一个带有non-static成员的基类。基本上,在该类别的继承体系中只会有一个类别带有non-static成员。

一个类、结构、联合只有在其是平凡的、匹配标准布局,并且所有非静态成员和基类都是POD时,才被视为POD。

通过划分,使得放弃一个特性而不失去另一个成为可能。一个具有复杂的复制和move构造函数的类可能不是平凡的,但是它可能匹配标准布局,从而能与C程序交互。类似地,一个同时具有public和private数据成员的类不匹配标准布局,但它可以是平凡的,从而能够使用memcpy来复制。

核心语言构造期表现的加强

外部模板

在标准C++中,只要在编译单元内遇到被完整定义的模板,编译器都必须将其实例化(instantiate)。这会大大增加编译时间,特别是模板在许多编译单元内使用相同的参数实例化。看起来没有办法告诉C++不要引发模板的实例化。

C++11将会引入外部模板这一概念。C++已经有了强制编译器在特定位置开始实例化的语法:

templateclassstd::vector;

而C++所缺乏的是阻止编译器在某个编译单元内实例化模板的能力。C++11将简单地扩充前文语法如下:

externtemplateclassstd::vector;

这样就告诉编译器不要在该编译单元内将该模板实例化。

使用时,如下例:

std::vectorva;

核心语言使用性的加强

这些特色存在的主要目的是为了使C++能够更容易使用。举凡可以增进类型安全,减少代码重复,不易误用代码之类的。

初始化列表

标准C++从C带来了初始化列表(initializer list)的概念。这个构想是结构或是数组能够依据成员在该结构内定义的顺序通过给予的一串引用来产生。这些初始化列表是递归的,所以结构的数组或是包含其他结构的结构可以使用它们。这对静态列表或是仅是把结构初始化为某值而言相当有用。C++有构造函数,能够重复对象的初始化。但单单只有那样并不足以替换这项特色的所有机能。在C++03中,只允许在严格遵守POD的定义和限制条件的结构及类别上使用这项机能,非POD的类型不能使用,就连相当有用的STL容器std::vector也不行。

C++11将会把初始化列表的概念绑到类型上,称作std::initializer_list。这允许构造函数或其他函数像参数般地使用初始化列表。举例来说:

classSequenceClass{public:SequenceClass(std::initializer_listlist);};

这将允许SequenceClass由一连串的整数构造,就像:

SequenceClasssomeVar={1,4,5,6};

这个构造函数是种特殊的构造函数,称作初始化列表构造函数。有着这种构造函数的类别在统一初始化的时候会被特别对待。

类别std::initializer_list是个第一级的C++11标准程序库类型。然而他们只能够经由C++11编译器通过{}语法的使用被静态地构造。这个列表一经构造便可复制,虽然这只是copy-by-reference。初始化列表是常数;一旦被创建,其成员均不能被改变,成员中的数据也不能够被变动。

因为初始化列表是真实类型,除了类别构造函数之外还能够被用在其他地方。正规的函数能够使用初始化列表作为形参。例如:

voidFunctionName(std::initializer_listlist);FunctionName({1.0f,-3.45f,-0.4f});

标准容器也能够以这种方式初始化:

vectorv={"xyzzy","plugh","abracadabra"};

统一的初始化

标准C++在初始化类型方面有着许多问题。初始化类型有数种方法,而且交换使用时不会都产生相同结果。传统的构造函数语法,看起来像是函数声明,而且为了能使编译器不会弄错必须采取一些步骤。只有集合体和POD类型能够被集合式的初始化(使用SomeType var = {/*stuff*/};)。

C++11将会提供一种统一的语法初始化任意的对象,它扩充了初始化列表语法:

structBasicStruct{intx;floaty;};structAltStruct{AltStruct(int_x,float_y):x(_x),y(_y){}private:intx;floaty;};BasicStructvar1{5,3.2f};AltStructvar2{2,4.3f};

var1的初始化的运作就如同C-style的初始化列表。每个公开的参数将被对应于初始化列表的值给初始化。隐式类型转换会在需要的时候被使用,这里的隐式类型转换不会产生范围缩限(narrowing)。要是不能够转换,编译便会失败。(范围缩限 (narrowing):转换后的类型无法表示原类型。如将32-bit的整数转换为16-bit或8-bit整数,或是浮点数转换为整数。)var2的初始化则是简单地调用构造函数。

统一的初始化构造能够免除具体指定特定类型的必要:

structIdString{std::stringname;intidentifier;};IdStringvar3{"SomeName",4};

该语法将会使用const char *参数初始化std::string。你也可以做像下面的事:

IdStringGetString(){return{"SomeName",4};// 注意這裡不需要明確的型別}

统一初始化不会替换构造函数语法。仍然会有需要用到构造函数语法的时候。如果一个类别拥有初始化列表构造函数(TypeName(initializer_list);),而初始化列表与构造函数的参数类型一致,那么它比其他形式的构造函数的优先权都来的高。C++11版本的std::vector将会有初始化列表构造函数。这表示:

std::vectortheVec{4};

这将会调用初始化列表构造函数,而不是调用std::vector只接受一个尺寸参数产生相应尺寸vector的构造函数。要使用这个构造函数,用户必须直接使用标准的构造函数语法。

类型推导

在标准C++和C,使用参数必须明确的指出其类型。然而,随着模版类型的出现以及模板元编程的技巧,某物的类型,特别是函数定义明确的回返类型,就不容易表示。在这样的情况下,将中间结果存储于参数是件困难的事,可能会需要知道特定的元编程程序库的内部情况。

C++11提供两种方法缓解上述所遇到的困难。首先,有被明确初始化的参数可以使用auto关键字。这会依据该初始化子(initializer)的具体类型产生参数:

autosomeStrangeCallableType=boost::bind(&SomeFunction,_2,_1,someObject);autootherVariable=5;

someStrangeCallableType的类型就是模板函数boost::bind对特定引用所回返的类型。作为编译器语义分析责任的一部分,这个类型能够简单地被编译器决定,但用户要通过查看来判断类型就不是那么容易的一件事了。

otherVariable的类型同样也是定义明确的,但用户很容易就能判别。它是个int(整数),就和整数字面值的类型一样。

除此之外,decltype能够被用来在编译期决定一个表示式的类型。举例:

intsomeInt;decltype(someInt)otherIntegerVariable=5;

decltype和auto一起使用会更为有用,因为auto参数的类型只有编译器知道。然而decltype对于那些大量运用运算符重载和特化的类型的代码的表示也非常有用。

auto对于减少冗赘的代码也很有用。举例而言,程序员不用写像下面这样:

for(vector::const_iteratoritr=myvec.cbegin();itr!=myvec.cend();++itr)

而可以用更简短的

for(autoitr=myvec.cbegin();itr!=myvec.cend();++itr)

这项差异随着程序员开始嵌套容器而更为显著,虽然在这种情况下typedef是一个减少代码的好方法。

decltype所表示的类型可以和auto推导出来的不同。

#includeintmain(){conststd::vectorv(1);autoa=v[0];// a為int型別decltype(v[0])b=0;// b為const int&型別,即// std::vector::operator[](size_type)const的回返型別autoc=0;// c為int型別autod=c;// d為int型別 decltype(c)e;// e為int型別,c實體的型別 decltype((c))f=e;// f為int&型別,因為(c)是左值decltype(0)g;// g為int型別,因為0是右值}

基于范围的for循环

for语句将允许简单的范围迭代:

intmy_array[5]={1,2,3,4,5};// double the value of each element in my_array:for(int&x:my_array){x*=2;}// similar but also using type inference for array elementsfor(auto&x:my_array){x*=2;}

上面for述句的第一部分定义被用来做范围迭代的变量,就像被声明在一般for循环的变量一样,其作用域仅只于循环的范围。而在":"之后的第二区块,代表将被迭代的范围。这种for语句还可以用于C型数组,初始化列表,和任何定义了begin()和end()来回返首尾迭代器的类型。

Lambda函数与表示式

在标准C++,特别是当使用C++标准程序库算法函数诸如sort和find,用户经常希望能够在算法函数调用的附近定义一个临时的述部函数(又称谓词函数,predicate function)。由于语言本身允许在函数内部定义类别,可以考虑使用函数对象,然而这通常既麻烦又冗赘,也阻碍了代码的流程。此外,标准C++不允许定义于函数内部的类别被用于模板,所以前述的作法是不可行的。

C++11对lambda的支持可以解决上述问题。

一个lambda函数可以用如下的方式定义:

[](intx,inty){returnx+y;}

这个不具名函数的回返类型是decltype(x+y)。只有在lambda函数匹配"return expression"的形式下,它的回返类型才能被忽略。在前述的情况下,lambda函数仅能为一个述句。

在一个更为复杂的例子中,回返类型可以被明确的指定如下:

[](intx,inty)->int{intz=x+y;returnz+x;}

本例中,一个临时的参数z被创建用来存储中间结果。如同一般的函数,z的值不会保留到下一次该不具名函数再次被调用时。

如果lambda函数没有传回值(例如void),其回返类型可被完全忽略。

定义在与lambda函数相同作用域的参数引用也可以被使用。这种的参数集合一般被称作closure(闭包)。

[]// 沒有定义任何变量。使用未定义变量会引发错误。[x,&y]// x以传值方式传入(默认),y以引用方式传入。[&]// 任何被使用到的外部变量都隐式地以引用方式加以引用。[=]// 任何被使用到的外部变量都隐式地以传值方式加以引用。[&,x]// x显式地以传值方式加以引用。其余变量以引用方式加以引用。[=,&z]// z显式地以引用方式加以引用。其余变量以传值方式加以引用。

closure被定义与使用如下:

std::vectorsomeList;inttotal=0;std::for_each(someList.begin(),someList.end(),[&total](intx){total+=x;});std::cout<<total;

上例可计算someList元素的总和并将其印出。参数total是lambda函数closure的一部分,同时它以引用方式被传递入谓词函数,因此它的值可被lambda函数改变。

若不使用引用的符号&,则代表参数以传值的方式传入lambda函数。让用户可以用这种表示法明确区分参数传递的方法:传值,或是传引用。由于lambda函数可以不在被声明的地方就地使用(如置入std::function对象中); 这种情况下,若参数是以传引用的方式链接到closure中,是无意义甚至是危险的行为。

若lambda函数只在定义的作用域使用,则可以用[&]声明lambda函数,代表所有引用到stack中的参数,都是以引用的方式传入,不必一一显式指明:

std::vectorsomeList;inttotal=0;std::for_each(someList.begin(),someList.end(),[&](intx){total+=x;});

参数传入lambda函数的方式可能随实现有所变化,一般期望的方法是lambda函数能保留其作用域函数的stack指针,借此访问区域参数。

若使用[=]而非[&],则代表所有的引用的参数都是传值使用。

对于不同的参数,传值或传引用可以混和使用。比方说,用户可以让所有的参数都以传引用的方式使用,但带有一个传值使用的参数:

inttotal=0;intvalue=5;[&,value](intx){total+=(x*value);};

total是传引用的方式传入lambda函数,而value则是传值。

若一个lambda函数被定义于某类别的成员函数中,则可以使用该类别对象的引用,并且能够访问其内部的成员。

[](SomeType*typePtr){typePtr->SomePrivateMemberFunction();};

这只有当该lambda函数创建的作用域是在SomeType的成员函数内部时才能运作。

在成员函数中指涉对象的this指针,必须要显式的传入lambda函数,否则成员函数中的lambda函数无法使用任何该对象的参数或函数。

[this](){this->SomePrivateMemberFunction();};

若是lambda函数使用[&]或是[=]的形式,this在lambda函数即为可见。

lambda函数是编译器从属类型的函数对象;这种类型名称只有编译器自己能够使用。如果用户希望将lambda函数作为参数传入,该类型必须是模版类型,或是必须创建一个std::function去获取lambda的值。使用auto关键字让我们能够存储lambda函数:

automyLambdaFunc=[this](){this->SomePrivateMemberFunction();};automyOnheapLambdaFunc=newauto([=]{/*...*/});

回返类型后置的函数声明

标准C函数声明语法对于C语言已经足够。演化自C的C++除了C的基础语法外,又扩充额外的语法。然而,当C++变得更为复杂时,它暴露出许多语法上的限制,特别是针对函数模板的声明。下面的示例,不是合法的C++03:

templateRetAddingFunc(constLHS&lhs,constRHS&rhs){returnlhs+rhs;}//Ret的型別必須是(lhs+rhs)的型別

Ret的类型由LHS与RHS相加之后的结果的类型来决定。即使使用C++11新加入的decltype来声明AddingFunc的回返类型,依然不可行。

templatedecltype(lhs+rhs)AddingFunc(constLHS&lhs,constRHS&rhs){returnlhs+rhs;}//不合法的C++11

不合法的原因在于lhs及rhs在定义前就出现了。直到剖析器解析到函数原型的后半部,lhs与rhs才是有意义的。

针对此问题,C++11引进一种新的函数定义与声明的语法:

templateautoAddingFunc(constLHS&lhs,constRHS&rhs)->decltype(lhs+rhs){returnlhs+rhs;}

这种语法也能套用到一般的函数定义与声明:

structSomeStruct{autoFuncName(intx,inty)->int;};autoSomeStruct::FuncName(intx,inty)->int{returnx+y;}

关键字auto的使用与其在自动类型推导代表不同的意义。

对象构造的改良

在标准C++中,构造函数不能调用其它的构造函数;每个构造函数必须自己初始化所有的成员或是调用一个共用的成员函数。基类的构造函数不能够直接作为派生类的构造函数;就算基类的构造函数已经足够,每个派生的类别仍必须实现自己的构造函数。类别中non-constant的数据成员不能够在声明的地方被初始化,它们只能在构造函数中被初始化。 C++11将会提供这些问题的解决方案。

C++11允许构造函数调用其他构造函数,这种做法称作委托或转接(delegation)。仅仅只需要加入少量的代码,就能让数个构造函数之间达成功能复用(reuse)。Java以及C♯都有提供这种功能。C++11语法如下:

classSomeType{intnumber;stringname;SomeType(inti,string&s):number(i),name(s){}public:SomeType():SomeType(0,"invalid"){}SomeType(inti):SomeType(i,"guest"){}SomeType(string&s):SomeType(1,s){PostInit();}};

C++03中,构造函数运行结束代表对象构造完成; 而允许使用转接构造函数的C++11则是以"任何"一个构造函数结束代表构造完成。使用委托的构造函数,函数本体中的代码将于被委托的构造函数完成后继续运行(如上例的PostInit())。若基类使用了委托构造函数,则派生类的构造函数会在"所有"基底类别的构造函数都完成后,才会开始运行。

C++11允许派生类手动继承基底类别的构造函数,编译器可以使用基底类别的构造函数完成派生类的构造。而将基类的构造函数带入派生类的动作,无法选择性地部分带入,要不就是继承基类全部的构造函数,要不就是一个都不继承(不手动带入)。此外,若牵涉到多重继承,从多个基底类别继承而来的构造函数不可以有相同的函数签名(signature)。而派生类的新加入的构造函数也不可以和继承而来的基底构造函数有相同的函数签名,因为这相当于重复声明。

语法如下:

classBaseClass{public:BaseClass(intiValue);};classDerivedClass:publicBaseClass{public:usingBaseClass::BaseClass;};

此语法等同于DerivedClass声明一个DerivedClass(int)的构造函数。同时也因为DerivedClass有了一个继承而来的构造函数,所以不会有默认构造函数。

另一方面,C++11可以使用以下的语法完成成员初始化:

classSomeClass{public:SomeClass(){}explicitSomeClass(intiNewValue):iValue(iNewValue){}private:intiValue=5;};

若是构造函数中没有设置iValue的初始值,则会采用类定义中的成员初始化,令iValue初值为5。在上例中,无参数版本的构造函数,iValue便采用默认所定义的值;而带有一个整数参数的构造函数则会以指定的值完成初始化。

成员初始化除了上例中的赋值形式(使用"="(,也可以采用构造函数以及统一形的初始化(uniform initialization,使用"{}")。

显式虚函数重载

在C++里,在子类中容易意外的重载虚函数。举例来说:

structBase{virtualvoidsome_func();};structDerived:Base{voidsome_func();};

Derived::some_func的真实意图为何?程序员真的试图重载该虚函数,或这只是意外?这也可能是base的维护者在其中加入了一个与Derived::some_func同名且拥有相同签名的虚函数。

另一个可能的状况是,当基类中的虚函数的签名被改变,子类中拥有旧签名的函数就不再重载该虚函数。因此,如果程序员忘记修改所有子类,运行期将不会正确调用到该虚函数正确的实现。

C++11将加入支持用来防止上述情形产生,并在编译期而非运行期捕获此类错误。为保持向后兼容,此功能将是选择性的。其语法如下:

structBase{virtualvoidsome_func(float);};structDerived:Base{virtualvoidsome_func(int)override;// 錯誤格式:Derive::some_func並沒有override Base::some_funcvirtualvoidsome_func(float)override;// OK:顯式改寫};

编译器会检查基底类别是否存在一虚拟函数,与派生类中带有声明override的虚拟函数,有相同的函数签名(signature);若不存在,则会回报错误。

C++11也提供指示字final,用来避免类别被继承,或是基底类别的函数被改写:

structBase1final{};structDerived1:Base1{};// 錯誤格式:class Base1已標明為finalstructBase2{virtualvoidf()final;};structDerived2:Base2{voidf();// 錯誤格式:Base2::f已標明為final};

以上的示例中,virtual void f() final;声明一新的虚拟函数,同时也表明禁止派生函数改写原虚拟函数。

override与final都不是语言关键字(keyword),只有在特定的位置才有特别含意,其他地方仍旧可以作为一般指示字(identifier)使用。

空指针

早在1972年,C语言诞生的初期,常数0带有常数及空指针的双重身份。 C使用preprocessor macro NULL表示空指针,让NULL及0分别代表空指针及常数0。 NULL可被定义为((void*)0)或是0。

C++并不采用C的规则,不允许将void*隐式转换为其他类型的指针。为了使代码char* c = NULL;能通过编译,NULL只能定义为0。这样的决定使得函数重载无法区分代码的语义:

voidfoo(char*);voidfoo(int);

C++建议NULL应当定义为0,所以foo(NULL);将会调用foo(int),这并不是程序员想要的行为,也违反了代码的直观性。0的歧义在此处造成困扰。

C++11引入了新的关键字来代表空指针常数:nullptr,将空指针和整数0的概念拆开。 nullptr的类型为nullptr_t,能隐式转换为任何指针或是成员指针的类型,也能和它们进行相等或不等的比较。而nullptr不能隐式转换为整数,也不能和整数做比较。

为了向下兼容,0仍可代表空指针常数。

char*pc=nullptr;// OKint*pi=nullptr;// OKinti=nullptr;// errorfoo(pc);// 呼叫foo(char *)

强类型枚举

在标准C++中,枚举类型不是类型安全的。枚举类型被视为整数,这使得两种不同的枚举类型之间可以进行比较。C++03唯一提供的安全机制是一个整数或一个枚举型值不能隐式转换到另一个枚举别型。此外,枚举所使用整数类型及其大小都由实现方法定义,皆无法明确指定。最后,枚举的名称全数暴露于枚举类型的作用域中,因此两个不同的枚举,不可以有相同的枚举名。(好比 enum Side{ Right, Left }; 和 enum Thing{ Wrong, Right }; 不能一起使用。)

C++11引进了一种特别的"枚举类",可以避免上述的问题。使用enum class的语法来声明:

enumclassmyEnumeration{Val1,Val2,Val3=100,Val4/* = 101 */,};

此种枚举为类型安全的。枚举类别不能隐式地转换为整数;也无法与整数数值做比较。(表示式Enumeration::Val4 == 101会触发编译期错误)。

枚举类别所使用类型必须显式指定。在上面的示例中,使用的是默认类型int,但也可以指定其他类型:

enumclassEnum2:unsignedint{Val1,Val2};

枚举类别的作用域(scoping)不包含枚举值的名字。使用枚举值的名字,必须明确限定于其所属的枚举类型。例如,前述枚举类别Enum2,Enum2::Val1是有意义的表示法,而单独的Val1则否。

此外,C++11允许为传统的枚举指定使用类型:

enumEnum3:unsignedlong{Val1=1,Val2};

枚举名Val1定义于Enum3的枚举范围中(Enum3::Val1),但为了向后兼容性, Val1仍然可以于所属枚举类型所在的作用域中单独使用。

在C++11中,枚举类别的前置声明(forward declaration)也是可行的,只要使用可指定类型的新式枚举即可。之前的C++无法写出枚举的前置声明,是由于无法确定枚举参数所占的空间大小,C++11解决了这个问题:

enumEnum1;// C++與C++11中不合法;無法判別大小enumEnum2:unsignedint;// 合法的C++11enumclassEnum3;// 合法的C++11,列舉類別使用預設型別int enumclassEnum4:unsignedint;// 合法的C++11enumEnum2:unsignedshort;// 不合法的C++11,Enum2已被聲明為unsigned int

角括号

标准C++的剖析器一律将">>"视为右移运算符。但在嵌套模板定义式中,绝大多数的场合其实都代表两个连续右角括号。为了避免剖析器误判,撰码时不能把右角括号连着写。

C++11变更了剖析器的解读规则;当遇到连续的右角括号时,会在合理的情况下将右尖括号解析为模板引用的结束符号。给使用>,>=,>>的表达式加上圆括号,可以避免其与圆括号外部的左尖括号相匹配:

templateSomeType;std::vector<SomeType2>>x1;// 解讀為std::vector of "SomeType 2>",// 非法的表示式,整數1被轉換為bool型別truestd::vector<SomeType2)>>x1;// 解讀為std::vector of "SomeType",// 合法的C++11表示式,(1>2)被轉換為bool型別false

显式类型转换子

C++引入了关键字explicit来避免用户自定的单引用构造函数被当成隐式类型转换子。但是,却没有限制明确定义的类型转换函数。比方说,一个smart pointer类别具有一个operator bool(),被定义成若该smart pointer不为null则传回true,反之传回false。遇到这样的代码时:if(smart_ptr_variable),编译器可以借由operator bool()隐式转换成布尔值,和测试原生指针的方法一样。但是这类隐式转换同样也会发生在非预期之处。由于C++的bool类型也是算术类型,能隐式换为整数甚至是浮点数。拿对象转换出的布尔值做布尔运算以外的数算,往往不是程序员想要的。

在C++11中,关键字explicit修饰符也能套用到类型转换函数上。如同构造函数一样,它能避免类型转换函数被隐式转换调用。但C++11特别指定,在if条件式、循环、逻辑运算等需要布尔值的地方,将其作为显式类型转换,因此即使对应的类型转换函数被explicit修饰也可以调用。这主要为了解决safe bool问题。

模板的别名

在进入这个主题之前,各位应该先弄清楚“模板”和“类型”本质上的不同。class template (类别模板,是模板)是用来产生template class(模板类别,是类型)。在传统的C++标准,typedef可定义模板类别一个新的类型名称,但是不能够使用typedef来定义模板的别名。举例来说:

templateclassSomeType;templatetypedefSomeTypeTypedefName;// 在C++是不合法的

这不能够通过编译。

为了定义模板的别名,C++11将会增加以下的语法:

templateclassSomeType;templateusingTypedefName=SomeType;

using也能在C++11中定义一般类型的别名,等同typedef:

typedefvoid(*PFD)(double);// 傳統語法usingPFD=void(*)(double);// 新增語法

无限制的unions

在C++03中,并非任意的类型都能做为union的成员。比方说,带有non-trivial 构造函数的类型就不能是union的成员。在新的标准里,移除了所有对union的使用限制,除了其成员仍然不能是引用类型。这一改变使得union更强大,更有用,也易于使用。

但是如果union成员具有非平凡的特殊成员函数,则编译器不会为union生成对应的特殊成员函数,必须手工定义。

以下为C++11中union使用的简单样例:

structpoint{point(){}point(intx,inty):x_(x),y_(y){}intx_,y_;};unionu1{intz;doublew;pointp;// 不合法的C++; point有一non-trivial建構式// 合法的C++11u1(intx,inty):p(x,y){};//Visual Studio 2015编译通过};

这一改变仅放宽union的使用限制,不会影响既有的旧代码。

核心语言能力的提升

这些特性让C++语言能够做一些以前做不到的,或者极其复杂的,或者需求一些不可移植的库的事情。

可变参数模板

在C++11之前,不论是类模板或是函数模板,都只能按其被声明时所指定的样子,接受一组固定数目的模板参数;C++11加入新的表示法,允许任意个数、任意类别的模板参数,不必在定义时将参数的个数固定。

templateclasstuple;

模板类tuple的对象,能接受不限个数的typename作为它的模板形参:

classtuple<int,std::vector,std::map<std::string,std::vector>>someInstanceName;

实参的个数也可以是0,所以class tuple someInstanceName这样的定义也是可以的。

若不希望产生实参个数为0的不定长参数模板,则可以采用以下的定义:

templateclasstuple;

不定长参数模板也能运用到模板函数上。传统C中的printf函数,虽然也能达成不定个数的形参的调用,但其并非类别安全。以下的样例中,C++11除了能定义类别安全的变长参数函数外,还能让类似printf的函数能自然地处理非内置类别的对象。除了在模板参数中能使用...表示不定长模板参数外,函数参数也使用同样的表示法代表不定长参数。

templatevoidprintf(conststd::string&strFormat,Params...parameters);

其中,Params与parameters分别代表模板与函数的变长参数集合,称之为参数包(parameter pack)。参数包必须要和运算符"..."搭配使用,避免语法上的歧义。

变长参数模板中,变长参数包无法如同一般参数在类或函数中使用; 因此典型的手法是以递归的方法取出可用参数,参看以下的C++11 printf样例:

voidprintf(constchar*s){while(*s){if(*s=="%"&&*(++s)!="%")throwstd::runtime_error("invalid format string: missing arguments");std::cout<<*s++;}}templatevoidprintf(constchar*s,Tvalue,Args...args){while(*s){if(*s=="%"&&*(++s)!="%"){std::cout<<value;printf(*s?++s:s,args...);// 即便当*s == 0也会产生调用,以检测更多的类型参数。return;}std::cout<<*s++;}throwstd::logic_error("extra arguments provided to printf");}

printf会不断地递归调用自身:函数参数包args...在调用时,会被模板类别匹配分离为T value和Args... args。直到args...变为空参数,则会与简单的printf(const char *s)形成匹配,结束递归。

另一个例子为计算模板参数的个数,这里使用相似的技巧展开模板参数包Args...:

templatestructCount{};templatestructcount{staticconstintvalue=0;};templatestructcount{staticconstintvalue=1+count::value;};

虽然没有一个简洁的机制能够对变长参数模板中的值进行迭代,但使用运算符"..."还能在代码各处对参数包施以更复杂的展开操作。举例来说,一个模板类的定义:

templateclassClassName:publicBaseClasses...{public:ClassName(BaseClasses&&...baseClasses):BaseClasses(baseClasses)...{}}

BaseClasses...会被展开成类别ClassName的基底类;ClassName的构造函数需要所有基类的右值引用,而每一个基类都是以传入的参数做初始化(BaseClasses(baseClasses)...)。

在函数模板中,变长参数可以和右值引用搭配,达成形参的完美转送(perfect forwarding):

templatestructSharedPtrAllocator{templatestd::shared_ptrConstructWithSharedPtr(Args&&...params){returntr1::shared_ptr(newTypeToConstruct(std::forward(params)...));}}

参数包parms可展开为TypeToConstruct构造函数的形参。表达式std::forward(params)可将形参的类别信息保留(利用右值引用),传入构造函数。而运算符"..."则能将前述的表达式套用到每一个参数包中的参数。这种工厂函数(factory function)的手法,使用std::shared_ptr管理配置对象的内存,避免了不当使用所产生的内存泄漏(memory leaks)。

此外,变长参数的数量可以藉以下的语法得知:

templatestructSomeStruct{staticconstintsize=sizeof...(Args);}

SomeStruct::size是2,而SomeStruct::size会是0。(sizeof...(Args)的结果是编译期常数。)

新的字符串字面值

标准C++提供了两种字符串字面值。第一种,包含有双引号,产生以空字符结尾的const char数组。第二种有着前标L,产生以空字符结尾的const wchar_t数组,其中wchar_t代表宽字符。对于Unicode编码的支持尚付阙如。

为了加强C++编译器对Unicode的支持,类别char的定义被修改为其大小至少能够存储UTF-8的8位编码,并且能够容纳编译器的基本字符集的任何成员。

C++11将支持三种Unicode编码方式:UTF-8,UTF-16,和UTF-32。除了上述char定义的变更,C++11将增加两种新的字符类别:char16_t和char32_t。它们各自被设计用来存储UTF-16以及UTF-32的字符。

以下展示如何产生使用这些编码的字符串字面值:

u8"I"m a UTF-8 string."u"This is a UTF-16 string."U"This is a UTF-32 string."

第一个字符串的类别是通常的const char[];第二个字符串的类别是const char16_t[];第三个字符串的类别是const char32_t[]。

当创建Unicode字符串字面值时,可以直接在字符串内插入Unicode codepoints。C++11提供了以下的语法:

u8"This is a Unicode Character: \u2018."u"This is a bigger Unicode Character: \u2018."U"This is a Unicode Character: \u2018."

在"\u"之后的是16个比特的十六进制数值;它不需要"0x"的前标。识别字"\u"代表了一个16位的Unicode codepoint;如果要输入32位的codepoint,使用"\U"和32个比特的十六进制数值。只有有效的Unicode codepoints能够被输入。举例而言,codepoints在范围U+D800—U+DFFF之间是被禁止的,它们被保留给UTF-16编码的surrogate pairs。

有时候避免手动将字符串换码也是很有用的,特别是在使用XML文件或是一些脚本语言的字面值的时候。C++11将提供raw(原始)字符串字面值:

在第一个例子中,任何包含在( )括号(标准已经从[]改为())当中的都是字符串的一部分。其中"和\字符不需要经过转义。在第二个例子中,"delimiter(开始字符串,只有在遇到)delimiter"才代表结束。其中delimiter可以是最多16个字符的任意的字符串(包含空字符串),但不能包含空格、控制字符和"("、")"、"\"。原始字符串允许用户使用圆括号(,),例如R"delimiter((a-z))delimiter"等价于"(a-z)"。原始字符串字面值能够和宽字面值或是Unicode字面值结合:

u8R"XXX(I"m a "rawUTF-8" string.)XXX"uR"*@(This is a "rawUTF-16" string.)*@"UR"(This is a "rawUTF-32" string.)"

用户定义字面量

标准C++提供了数种字面值。字符"12.5"是能够被编译器解释为数值12.5的double类别字面值。然而,加上"f"的后置,像是"12.5f",则会产生数值为12.5的float类别字面值。之前的C++规范中字面值的修饰符是固定的,C++代码不能创立新的字面修饰符。

C++11开放用户定义新的字面修饰符(literal modifier),利用自定义的修饰符完成由字面值构造对象。

字面值转换可以定义为两个阶段:原始与转换后(raw与cooked)。原始字面值指特定类型的字符序列,而转换后的字面值则代表另一种类别。如字面值1234,原始字面值是"1", "2", "3", "4"的字符序列;而转换后的字面值是整数值1234。另外,字面值0xA转换前是序列"0", "x", "A";转换后代表整数值10。

多任务内存模型

C++标准委员会计划统一对多线程编程的支持。

这将涉及两个部分:第一、设计一个可以使多个线程在一个进程中共存的内存模型;第二、为线程之间的交互提供支持。第二部分将由程序库提供支持,更多请看线程支持。

在多个线程可能会访问相同内存的情形下,由一个内存模型对它们进行调度是非常有必要的。遵守模型规则的程序是被保证正确运行的,但违反规则的程序会发生不可预料的行为,这些行为依赖于编译器的最优化和内存一致性的问题。

thread-local的存储期限

在多线程环境下,让各线程拥有各自的参数是很普遍的。这已经存在于函数的区域参数,但是对于全域和静态参数都还不行。

新的thread_local存储期限(在现行的static、dynamic和automatic之外)被作为下个标准而提出。线程区域的存储期限会借由存储指定字thread_local来表明。

static对象(生命周期为整个程序的运行期间)的存储期限可以被thread-local给替代。就如同其他使用static存储期的参数,thread-local对象能够以构造函数初始化并以析构函数摧毁。

使用或禁用对象的默认函数

在传统C++中,若用户没有提供,则编译器会自动为对象生成默认构造函数(default constructor)、复制构造函数(copy constructor),赋值运算符(copy assignment operator operator=)以及析构函数(destructor)。另外,C++也为所有的类定义了数个全域运算符(如operator delete及operator new)。当用户有需要时,也可以提供自定义的版本改写上述的函数。

问题在于原先的c++无法精确地控制这些默认函数的生成。比方说,要让类别不能被拷贝,必须将复制构造函数与赋值运算符声明为private,并不去定义它们。尝试使用这些未定义的函数会导致编译期或链接期的错误。但这种手法并不是一个理想的解决方案。

此外,编译器产生的默认构造函数与用户定义的构造函数无法同时存在。若用户定义了任何构造函数,编译器便不会生成默认构造函数; 但有时同时带有上述两者提供的构造函数也是很有用的。目前并没有显式指定编译器产生默认构造函数的方法。

C++11允许显式地表明采用或拒用编译器提供的内置函数。例如要求类别带有默认构造函数,可以用以下的语法:

structSomeType{SomeType()=default;// 預設建構式的顯式聲明SomeType(OtherTypevalue);};

另一方面,也可以禁止编译器自动产生某些函数。如下面的例子,类别不可复制:

structNonCopyable{NonCopyable&operator=(constNonCopyable&)=delete;NonCopyable(constNonCopyable&)=delete;NonCopyable()=default;};

禁止类别以operator new配置内存:

structNonNewable{void*operatornew(std::size_t)=delete;};

此种对象只能生成于stack中或是当作其他类别的成员,它无法直接配置于heap之中,除非使用了与平台相关,不可移植的手法。(使用placement new运算符虽然可以在用户自配置的内存上调用对象构造函数,但在此例中其他形式的new运算符一并被上述的定义屏蔽("name hiding"),所以也不可行。)

= delete的声明(同时也是定义)也能适用于非内置函数,禁止成员函数以特定的形参调用:

structNoDouble{voidf(inti);voidf(double)=delete;};

若尝试以double的形参调用f(),将会引发编译期错误,编译器不会自动将double形参转型为int再调用f()。若要彻底的禁止以非int的形参调用f(),可以将= delete与模板相结合:

structOnlyInt{voidf(inti);templatevoidf(T)=delete;};

long long int类别

在32位系统上,一个long long int是保有至少64个有效比特的整数类别。C99将这个类别引入了标准C中,目前大多数的C++编译器也支持这种类别。C++11将把这种类别添加到标准C++中。

静态assertion

C++提供了两种方法测试assertion(声明):宏assert以及预处理器指令#error。但是这两者对于模版来说都不合用。宏在运行期测试assertion,而预处理器指令则在前置处理时测试assertion,这时候模版还未能实例化。所以它们都不适合来测试牵扯到模板参数的相关特性。

新的机能会引进新的方式可以在编译期测试assertion,只要使用新的关键字static_assert。声明采取以下的形式:

这里有一些如何使用static_assert的例子:

static_assert(3.14<GREEKPI&&GREEKPI<3.15,"GREEKPI is inaccurate!");

templatestructCheck{static_assert(sizeof(int)<=sizeof(T),"T is not big enough!");};

当常数表达式值为false时,编译器会产生相应的错误消息。第一个例子是预处理器指令#error的替代方案;第二个例子会在每个模板类别Check生成时检查assertion。

静态assertion在模板之外也是相当有用的。例如,某个算法的实现依赖于long long类别的大小比int还大,这是标准所不保证的。这种假设在大多数的系统以及编译器上是有效的,但不是全部。

允许sizeof运算符作用在类别的数据成员上,无须明确的对象

在标准C++,sizeof可以作用在对象以及类别上。但是不能够做以下的事:

structSomeType{OtherTypemember;};sizeof(SomeType::member);// 直接由SomeType型別取得非靜態成員的大小,C++03不行。C++11允許

这会传回OtherType的大小。C++03并不允许这样做,所以会引发编译错误。C++11将会允许这种使用。

垃圾回收机制

是否会自动回收那些无法被使用到(unreachable)的动态分配对象由实现决定。

C++标准程序库的变更

C++11标准程序库有数个新机能。其中许多可以在现行标准下实现,而另外一些则依赖于(或多或少)新的C++11核心语言机能。

新的程序库的大部分被定义于C++标准委员会的Library Technical Report(称TR1),于2005年发布。各式TR1的完全或部分实现目前提供在名字空间std::tr1。C++11会将其移置于名字空间std之下。

标准库组件上的升级

目前的标准库能受益于C++11新增的一些语言特性。举例来说,对于大部分的标准库容器而言,像是搬移内含大量元素的容器,或是容器之内对元素的搬移,基于右值引用(Rvalue reference)的move构造函数都能优化前述动作。在适当的情况下,标准库组件将可利用C++11的语言特性进行升级。这些语言特性包含但不局限以下所列:

右值引用和其相关的move支持

支持UTF-16编码,和UTF-32字符集

变长参数模板(与右值引用搭配可以达成完美转发(perfect forwarding))

编译期常数表达式

Decltype

显式类别转换子

使用或禁用对象的默认函数

此外,自C++标准化之后已经过许多年。现有许多代码利用到了标准库;这同时揭露了部分的标准库可以做些改良。其中之一是标准库的内存配置器(allocator)。C++11将会加入一个基于作用域模型的内存配置器来支持现有的模型。

线程支持

虽然C++11会在语言的定义上提供一个内存模型以支持线程,但线程的使用主要将以C++11标准库的方式呈现。

C++11标准库会提供类别thread(std::thread)。若要运行一个线程,可以创建一个类别thread的实体,其初始参数为一个函数对象,以及该函数对象所需要的参数。通过成员函数std::thread::join()对线程会合的支持,一个线程可以暂停直到其它线程运行完毕。若有底层平台支持,成员函数std::thread::native_handle()将可提供对原生线程对象运行平台特定的操作。

对于线程间的同步,标准库将会提供适当的互斥锁(像是std::mutex,std::recursive_mutex等等)和条件参数(std::condition_variable和std::condition_variable_any)。前述同步机制将会以RAII锁(std::lock_guard和std::unique_lock)和锁相关算法的方式呈现,以方便程序员使用。

对于要求高性能,或是极底层的工作,有时或甚至是必须的,我们希望线程间的通信能避免互斥锁使用上的开销。以原子操作来访问内存可以达成此目的。针对不同情况,我们可以通过显性的内存屏障改变该访问内存动作的可见性。

对于线程间异步的传输,C++11标准库加入了以及std::packaged_task用来包装一个会传回异步结果的函数调用。因为缺少结合数个future的功能,和无法判定一组promise集合中的某一个promise是否完成,futures此一提案因此而受到了批评。

更高级的线程支持,如线程池,已经决定留待在未来的Technical Report加入此类支持。更高级的线程支持不会是C++11的一部分,但设想是其最终实现将创建在目前已有的线程支持之上。

std::async提供了一个简便方法以用来运行线程,并将线程绑定在std::future。用户可以选择一个工作是要多个线程上异步的运行,或是在一个线程上运行并等待其所需要的数据。默认的情况,实现可以根据底层硬件选择前面两个选项的其中之一。另外在较简单的使用情形下,实现也可以利用线程池提供支持。

多元组类别

多元组是一个内由数个异质对象以特定顺序排列而成的数据结构。多元组可被视为是struct其数据成员的一般化。

由TR1演进而来的C++11多元组类别将受益于C++11某些特色像是可变参数模板。TR1版本的多元组类别对所能容纳的对象个数会因实现而有所限制,且实现上需要用到大量的宏技巧。相反的,C++11版本的多元组型基本上于对其能容纳的对象个数没有限制。然而,编译器对于模板实体化的递归深度上的限制仍旧影响了元组类别所能容纳的对象个数(这是无法避免的情况);C++11版本的多元组型不会把这个值让用户知道。

使用可变参数模板,多元组类别的声明可以长得像下面这样:

templateclasstuple;

底下是一个多元组类别的定义和使用情况:

typedefstd::tupletest_tuple;longlengthy=12;test_tupleproof(18,6.5,lengthy,"Ciao!");lengthy=std::get(proof);// 將proof的第一個元素賦值給lengthy(索引從零開始起跳)std::get(proof)=" Beautiful!";// 修改proof的第四個元素

我们可以定义一个多元组类别对象proof而不指定其内容,前提是proof里的元素其类别定义了默认构造函数(default constructor)。此外,以一个多元组类别对象赋值给另一个多元组类别对象是可能的,但只有在以下情况:若这两个多元组类别相同,则其内含的每一个元素其类别都要定义拷贝构造函数(copy constructor);否则的话,赋值操作符右边的多元组其内含元素的类别必须能转换成左边的多元组其对应的元素类别,又或者赋值操作符左边的多元组其内含元素的类别必须定义适当的构造函数。

std::tuplet1;std::tuplet2("X",2,"Hola!");t1=t2;// 可行。前兩個元素會作型別轉換,// 第三個字串元素可由"const char *"所建構。

多元组类型对象的比较运算是可行的(当它们拥有同样数量的元素)。此外,C++11提供两个表达式用来检查多元组类型的一些特性(仅在编译期做此检查)。

std::tuple_size::value回传多元组T内的元素个数,

std::tuple_element::type回传多元组T内的第I个元素的类别

散列表

在过去,不断有要求想将散列表(无序关系式容器)引进标准库。只因为时间上的限制,散列表才没有被标准库所采纳。虽然,散列表在最糟情况下(如果出现许多冲突(collision)的话)在性能上比不过平衡树。但实际运用上,散列表的表现则较佳。

因为标准委员会还看不到有任何机会能将开放定址法标准化,所以目前冲突仅能通过链地址法(linear chaining)的方式处理。为避免与第三方库发展的散列表发生名称上的冲突,前缀将采用unordered而非hash。

库将引进四种散列表,其中差别在于底下两个特性:是否接受具相同键值的项目(Equivalent keys),以及是否会将键值映射到相对应的数据(Associated values)。

上述的类别将满足对一个容器类别的要求,同时也提供访问其中元素的成员函数:insert,erase,begin,end。

散列表不需要对现有核心语言做扩展(虽然散列表的实现会利用到C++11新的语言特性),只会对头文件做些许扩展,并引入和两个头文件。对于其它现有的类别不会有任何修改。同时,散列表也不会依赖其它标准库的扩展功能。

正则表达式

过去许多或多或少标准化的程序库被创建用来处理正则表达式。有鉴于这些算法的使用非常普遍,因此标准程序库将会包含他们,并使用各种面向对象语言的潜力。

这个新的程序库,被定义于头文件,由几个新的类别所组成:

正则表达式(模式)以模板类basic_regex的实体表示

模式匹配的情况以模板类match_results的实体表示

函数regex_search是用来搜索模式;若要搜索并替换,则要使用函数regex_replace,该函数会回传一个新的字符串。算法regex_search和regex_replace接受一个正则表达式(模式)和一个字符串,并将该模式匹配的情况存储在struct match_results。

底下描述了match_results的使用情况:

constchar*reg_esp="[ ,.\\t\\n;:]";// 分隔字元列表std::regexrgx(reg_esp);// "regex"是樣板類"basic_regex"以型別為"char" // 的參數具現化的實體std::cmatchmatch;// "cmatch"是樣板類match_results"以型別為"const char *"// "的參數具現化的實體constchar*target="Polytechnic University of Turin ";// 辨別所有被分隔字元所分隔的字if(regex_search(target,match,rgx)){// 若此種字存在constsize_tn=match.size();for(size_ta=0;a<n;a++){stringstr(match[a].first,match[a].second);cout<<str<<"\n";}}

注意双反斜线的使用,因为C++将反斜线作为转义字符使用。但C++11的raw string可以用来避免此一问题。库不需要改动到现有的头文件,同时也不需要对现有的语言作扩展。

通用智能指针

这些指针是由TR1智能指针演变而来。注意! 智能指针是类别而非一般指针。

shared_ptr是一引用计数(reference-counted)指针,其行为与一般C++指针极为相似。在TR1的实现中,缺少了一些一般指针所拥有的特色,像是别名或是指针运算。C++11新增前述特色。

一个shared_ptr只有在已经没有任何其它shared_ptr指向其原本所指向对象时,才会销毁该对象。

一个weak_ptr指向的是一个被shared_ptr所指向的对象。该weak_ptr可以用来决定该对象是否已被销毁。weak_ptr不能被解引用;想要访问其内部所保存的指针,只能通过shared_ptr。有两种方法可达成此目的。第一,类别shared_ptr有一个以weak_ptr为参数的构造函数。第二,类别weak_ptr有一个名为lock的成员函数,其回返值为一个shared_ptr。weak_ptr并不拥有它所指向的对象,因此不影响该对象的销毁与否。

底下是一个shared_ptr的使用样例:

intmain(){std::shared_ptrp_first(newdouble);{std::shared_ptrp_copy=p_first;*p_copy=21.2;}// 此時"p_copy"會被銷毀,但動態分配的double不會被銷毀。return0;// 此時"p_first"會被銷毀,動態分配的double也會被銷毀(因為不再有指針指向它)。}

auto_ptr将会被C++标准所废弃,取而代之的是unique_ptr。unique_ptr提供auto_ptr大部分特性,唯一的例外是auto_ptr的不安全、隐性的左值搬移。不像auto_ptr,unique_ptr可以存放在C++11提出的那些能察觉搬移动作的容器之中。

可扩展的随机数功能

C标准库允许使用rand函数来生成伪随机数。不过其算法则取决于各程序库开发者。C++直接从C继承了这部分,但是C++11将会提供产生伪随机数的新方法。

C++11的随机数功能分为两部分:第一,一个随机数生成引擎,其中包含该生成引擎的状态,用来产生随机数。第二,一个分布,这可以用来决定产生随机数的范围,也可以决定以何种分布方式产生随机数。随机数生成对象即是由随机数生成引擎和分布所构成。

不同于C标准库的rand;针对产生随机数的机制,C++11将会提供三种算法,每一种算法都有其强项和弱项:

C++11将会提供一些标准分布:uniform_int_distribution(离散型均匀分布),bernoulli_distribution(伯努利分布),geometric_distribution(几何分布),poisson_distribution(卜瓦松分布),binomial_distribution(二项分布),uniform_real_distribution(离散型均匀分布),exponential_distribution(指数分布),normal_distribution(正态分布)和gamma_distribution(伽玛分布)。

底下描述一个随机数生成对象如何由随机数生成引擎和分布构成:

std::uniform_int_distributiondistribution(0,99);// 以離散型均勻分佈方式產生int亂數,範圍落在0到99之間std::mt19937engine;// 建立亂數生成引擎autogenerator=std::bind(distribution,engine);// 利用bind將亂數生成引擎和分布組合成一個亂數生成物件intrandom=generator();// 產生亂數

包装引用

我们可以通过实体化模板类reference_wrapper得到一个包装引用(wrapper reference)。包装引用类似于一般的引用。对于任意对象,我们可以通过模板类ref得到一个包装引用(至于constant reference则可通过cref得到)。

当模板函数需要形参的引用而非其拷贝,这时包装引用就能派上用场:

// 此函數將得到形參"r"的引用並對r加一voidf(int&r){r++;}// 樣板函式templatevoidg(Ff,Pt){f(t);}intmain(){inti=0;g(f,i);// 實體化"g" // "i"不會被修改std::cout<<i<"// "i"會被修改std::cout<<i<<std::endl;// 輸出1}

这项功能将加入头文件之中,而非通过扩展语言来得到这项功能。

多态函数对象包装器

针对函数对象的多态包装器(又称多态函数对象包装器)在语义和语法上和函数指针相似,但不像函数指针那么狭隘。只要能被调用,且其参数能与包装器兼容的都能以多态函数对象包装器称之(函数指针,成员函数指针或仿函数)。

通过以下例子,我们可以了解多态函数对象包装器的特性:

std::functionfunc;// 利用樣板類"function"// 建立包裝器std::plusadd;// "plus"被宣告為"template T plus( T, T ) ;"// 因此"add"的型別是"int add( int x, int y )"func=&add;// 可行。"add"的型參和回返值型別與"func"相符inta=func(1,2);// 注意:若包裝器"func"沒有參考到任何函式// 會丟出"std::bad_function_call"例外std::functionfunc2;if(!func2){// 因為尚未賦值與"func2"任何函式,此條件式為真booladjacent(longx,longy);func2=&adjacent;// 可行。"adjacent"的型參和回返值型別可透過型別轉換進而與"func2"相符structTest{booloperator()(shortx,shorty);};Testcar;func=std::ref(car);// 樣板類"std::ref"回傳一個struct "car"// 其成員函式"operator()"的包裝}func=func2;// 可行。"func2"的型參和回返值型別可透過型別轉換進而與"func"相符

模板类function将定义在头文件,而不须更动到语言本身。

用于元编程的类别属性

对于那些能自行创建或修改本身或其它程序的程序,我们称之为元编程。这种行为可以发生在编译或运行期。C++标准委员会已经决定引进一组由模板实现的库,程序员可利用此一库于编译期进行元编程。

底下是一个以元编程来计算指数的例子:

templatestructPow{// recursive call and recombination.enum{value=B*Pow::value};};templatestructPow{// ""N == 0"" condition of termination.enum{value=1};};intquartic_of_three=Pow::value;

许多算法能作用在不同的数据类别;C++模板支持泛型,这使得代码能更紧凑和有用。然而,算法经常会需要目前作用的数据类别的信息。这种信息可以通过类别属性(type traits)于模板实体化时将该信息萃取出来。

类别属性能识别一个对象的种类和有关一个类别(class或struct)的特征。头文件描述了我们能识别那些特征。

底下的例子说明了模板函数‘elaborate’是如何根据给定的数据类别,从而实体化某一特定的算法(algorithm.do_it)。

// 演算法一templatestructAlgorithm{templatestaticintdo_it(T1&,T2&){/*...*/}};// 演算法二templatestructAlgorithm{templatestaticintdo_it(T1,T2){/*...*/}};// 根據給定的型別,實體化之後的"elaborate"會選擇演算法一或二templateintelaborate(T1A,T2B){// 若T1為int且T2為float,選用演算法二// 其它情況選用演算法一returnAlgorithm<std::is_integral::value&&std::is_floating_point::value>::do_it(A,B);}

此种编程技巧能写出优美、简洁的代码;然而除错是此种编程技巧的弱处:编译期的错误消息让人不知所云,运行期的除错更是困难。

用于计算函数对象回返类型的统一方法

要在编译期决定一个模板仿函数的回返值类别并不容易,特别是当回返值依赖于函数的参数时。举例来说:

structClear{intoperator()(int);// 參數與回返值的型別相同doubleoperator()(double);// 參數與回返值的型別相同};templateclassCalculus{public:templateArgoperator()(Arg&a)const{returnmember(a);}private:Objmember;};

实体化模板类Calculus,Calculus的仿函数其回返值总是和Clear的仿函数其回返值具有相同的类别。然而,若给定类别Confused:

structConfused{doubleoperator()(int);// 參數與回返值的型別不相同intoperator()(double);// 參數與回返值的型別不相同};

企图实体化模板类Calculus将导致Calculus的仿函数其回返值和类别Confused的仿函数其回返值有不同的类别。对于int和double之间的转换,编译器将给出警告。

模板std::result_of被TR1引进且被C++11所采纳,可允许我们决定和使用一个仿函数其回返值的类别。底下,CalculusVer2对象使用std::result_of对象来推导其仿函数的回返值类别:

templateclassCalculusVer2{public:templatetypenamestd::result_of::typeoperator()(Arg&a)const{returnmember(a);}private:Objmember;};

如此一来,在实体化CalculusVer2其仿函数时,不会有类别转换,警告或是错误发生。

模板std::result_of在TR1和C++11有一点不同。TR1的版本允许实现在特殊情况下,可以无法决定一个函数调用其回返值类别。然而,因为C++11支持了decltype,实现被要求在所有情况下,皆能计算出回返值类别。

已被移除或是不包含在C++11标准的特色

预计由Technical Report提供支持:

模块

十进制类别

数学专用函数

延后讨论:

Concepts(概念 (C++))

更完整或必备的垃圾回收支持

Reflection

Macro Scopes

被移除或废弃的特色

循序点(sequence point),这个术语正被更为易懂的描述所替换。一个运算可以发生(is sequenced before)在另一个运算之前;又或者两个运算彼此之间没有顺序关系(are unsequenced)。

export

exception specifications

std::auto_ptr(英语:std::auto ptr)被std::unique_ptr替换。

仿函数基类别 (std::unary_function, std::binary_function)、函数指针适配器、类成员指针适配器以及绑定器 (binder)。

编译器实现

C++编译器对C++11新特性的支持情况:

Visual C++ 2010:C++0x Core Language Features In VC10: The Table

Visual C++ 2010与Visual C++ 2012支持的C++11特性的对比列表:C++11 Features (Modern C++)

Visual C++ 2013支持的C++11特性的对比列表:C++11 Features (Modern C++)

Visual C++ 2015预览版支持的C++11/14/17特性的对比列表:VC2015 Preview语言特性列表

GCC4.8.1已实现C++11标准的所有主要语言特性:Status of Experimental C++11 Support in GCC 4.8

关系项目

C++ Technical Report 1

C11,C编程语言的最新标准

C++14,C++的最新标准

参考资料

C++标准委员会文件

^ Doc No.1401: Jan Kristoffersen(2002年10月21日)Atomic operations with multi-threaded environments

^ Doc No.1402: Doug Gregor(2002年10月22日)A Proposal to add a Polymorphic Function Object Wrapper to the Standard Library

^ Doc No.1403: Doug Gregor(2002年11月8日)Proposal for adding tuple types into the standard library

^ Doc No.1424: John Maddock(2003年3月3日)A Proposal to add Type Traits to the Standard Library

^ Doc No.1429: John Maddock(2003年3月3日)A Proposal to add Regular Expression to the Standard Library

^ Doc No.1449: B. Stroustrup, G. Dos Reis, Mat Marcus, Walter E. Brown, Herb Sutter(2003年4月7日)Proposal to add template aliases to C++

^ Doc No.1450: P. Dimov, B. Dawes, G. Colvin(2003年3月27日)A Proposal to Add General Purpose Smart Pointers to the Library Technical Report (Revision 1)

^ Doc No.1452: Jens Maurer (April 10, 2003) A Proposal to Add an Extensible Random Number Facility to the Standard Library (Revision 2)

^ Doc No.1453: D. Gregor, P. Dimov (April 9, 2003) A proposal to add a reference wrapper to the standard library (revision 1)

^ Doc No.1454: Douglas Gregor, P. Dimov (April 9, 2003) A uniform method for computing function object return types (revision 1)

^ Doc No.1456: Matthew Austern (April 9, 2003) A Proposal to Add Hash Tables to the Standard Library (revision 4)

^ Doc No.1471: Daveed Vandevoorde (April 18, 2003) Reflective Metaprogramming in C++

^ Doc No.1676: Bronek Kozicki (September 9, 2004) Non-member overloaded copy assignment operator

^ Doc No.1704: Douglas Gregor, Jaakko Järvi, Gary Powell (September 10, 2004) Variadic Templates: Exploring the Design Space

^ Doc No.1705: J. Järvi, B. Stroustrup, D. Gregor, J. Siek, G. Dos Reis (September 12, 2004) Decltype (and auto)

^ Doc No.1717: Francis Glassborow, Lois Goldthwaite (November 5, 2004) explicit class and default definitions

^ Doc No.1719: Herb Sutter, David E. Miller (October 21, 2004) Strongly Typed Enums (revision 1)

^ Doc No.1720: R. Klarer, J. Maddock, B. Dawes, H. Hinnant (October 20, 2004) Proposal to Add Static Assertions to the Core Language (Revision 3)

^ Doc No.1757: Daveed Vandevoorde (January 14, 2005) Right Angle Brackets (Revision 2)

^ Doc No.1811: J. Stephen Adamczyk (April 29, 2005) Adding the long long type to C++ (Revision 3)

^ Doc No.1815: Lawrence Crowl (May 2, 2005) ISO C++ Strategic Plan for Multithreading

^ Doc No.1827: Chris Uzdavinis, Alisdair Meredith (August 29, 2005) An Explicit Override Syntax for C++

^ Doc No.1834: Detlef Vollmann (June 24, 2005) A Pleading for Reasonable Parallel Processing Support in C++

^ Doc No.1836: ISO/IEC DTR 19768 (June 24, 2005) Draft Technical Report on C++ Library Extensions

^ Doc No.1886: Gabriel Dos Reis, Bjarne Stroustrup (October 20, 2005) Specifying C++ concepts

^ Doc No.1891: Walter E. Brown (October 18, 2005) Progress toward Opaque Typedefs for C++0X

^ Doc No.1898: Michel Michaud, Michael Wong (October 6, 2004) Forwarding and inherited constructors

^ Doc No.1919: Bjarne Stroustrup, Gabriel Dos Reis (December 11, 2005) Initializer lists

^ Doc No.1968: V Samko; J Willcock, J Järvi, D Gregor, A Lumsdaine (February 26, 2006) Lambda expressions and closures for C++

^ Doc No.1986: Herb Sutter, Francis Glassborow (April 6, 2006) Delegating Constructors (revision 3)

^ Doc No.2016: Hans Boehm, Nick Maclaren (April 21, 2002) Should volatile Acquire Atomicity and Thread Visibility Semantics?

^ Doc No.2142: ISO/IEC DTR 19768 (January 12, 2007) State of C++ Evolution (between Portland and Oxford 2007 Meetings)

^ Doc No.2228: ISO/IEC DTR 19768 (May 3, 2007) State of C++ Evolution (Oxford 2007 Meetings)

^ Doc No.2258: G. Dos Reis and B. Stroustrup Templates Aliases

^ Doc No.2280: Lawrence Crowl (May 2, 2007) Thread-Local Storage

^ Doc No.2291: ISO/IEC DTR 19768 (June 25, 2007) State of C++ Evolution (Toronto 2007 Meetings)

^ Doc No.2336: ISO/IEC DTR 19768 (July 29, 2007) State of C++ Evolution (Toronto 2007 Meetings)

^ Doc No.2389: ISO/IEC DTR 19768 (August 7, 2007) State of C++ Evolution (pre-Kona 2007 Meetings)

^ Doc No.2431: SC22/WG21/N2431 = J16/07-0301 (October 2, 2007), A name for the null pointer: nullptr

^ Doc No.2432: ISO/IEC DTR 19768 (October 23, 2007) State of C++ Evolution (post-Kona 2007 Meeting)

^ Doc No.2437: Lois Goldthwaite (October 5, 2007) Explicit Conversion Operators

^ Doc No.2461: ISO/IEC DTR 19768 (October 22, 2007) Working Draft, Standard for programming Language C++

^ Doc No.2507: ISO/IEC DTR 19768 (February 4, 2008) State of C++ Evolution (pre-Bellevue 2008 Meeting)

^ Doc No.2544: Alan Talbot, Lois Goldthwaite, Lawrence Crowl, Jens Maurer (February 29, 2008) Unrestricted unions

^ Doc No.2565: ISO/IEC DTR 19768 (March 7, 2008) State of C++ Evolution (post-Bellevue 2008 Meeting)

^ Doc No.2597: ISO/IEC DTR 19768 (April 29, 2008) State of C++ Evolution (pre-Antipolis 2008 Meeting)

^ Doc No.2606: ISO/IEC DTR 19768 (May 19, 2008) Working Draft, Standard for Programming Language C++

^ Doc No.2697: ISO/IEC DTR 19768 (June 15, 2008) Minutes of WG21 Meeting June 8–15, 2008

^ Doc No.2798: ISO/IEC DTR 19768 (October 4, 2008) Working Draft, Standard for Programming Language C++

^ Doc No.2857: ISO/IEC DTR 19768 (March 23, 2009) Working Draft, Standard for Programming Language C++

^ Doc No.2869: ISO/IEC DTR 19768 (April 28, 2009) State of C++ Evolution (post-San Francisco 2008 Meeting)

^ Doc No.3000: ISO/ISC DTR 19769 (November 9, 2009) Working Draft, Standard for Programming Language C++

^ Doc No.3014: Stephen D. Clamage (November 4, 2009) AGENDA, PL22.16 Meeting No. 53, WG21 Meeting No. 48, March 8–13, 2010, Pittsburgh, PA

^ Doc No.3082: Herb Sutter(2010年3月13日)C++0x Meeting Schedule

^ Doc No.3092: ISO/ISC DTR 19769(2010年3月26日)Working Draft, Standard for Programming Language C++

^ Doc No.3126: ISO/ISC DTR 19769(2010年8月21日)Working Draft, Standard for Programming Language C++

^ Doc No.3225: ISO/ISC DTR 19769(2010年11月27日)Working Draft, Standard for Programming Language C++

^ Doc No.3242: ISO/ISC DTR 19769(2011年2月28日)Working Draft, Standard for Programming Language C++

^ Doc No.3290: ISO/ISC DTR 19769(2011年4月11日)Working Draft, Standard for Programming Language C++

文章

ab The C++ Source Bjarne Stroustrup(2006年1月2日)A Brief Look at C++0x

^ C/C++ Users Journal Bjarne Stroustrup (May, 2005) The Design of C++0x: Reinforcing C++’s proven strengths, while moving into the future

Web Log di Raffaele Rialdi(2005年9月16日)Il futuro di C++ raccontato da Herb Sutter

Informit.com(2006年8月5日)The Explicit Conversion Operators Proposal

Informit.com(2006年7月25日)Introducing the Lambda Library

Dr. Dobb"s Portal Pete Becker(2006年4月11日)Regular Expressions TR1"s regex implementation

Informit.com(2006年7月25日)The Type Traits Library

Dr. Dobb"s Portal Pete Becker(2005年5月11日)C++ Function Objects in TR1

The C++ Source Howard E. Hinnant, Bjarne Stroustrup, and Bronek Kozicki(2008年3月10日)A Brief Introduction to Rvalue References

DevX.com Special Report(2008年8月18日)C++0x: The Dawning of a New Standard


免责声明:以上内容版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。感谢每一位辛勤著写的作者,感谢每一位的分享。

——— 没有了 ———
编辑:阿族小谱

相关资料

展开
发表评论
写好了,提交
{{item.label}}
{{commentTotal}}条评论
{{item.userName}}
发布时间:{{item.time}}
{{item.content}}
回复
举报
点击加载更多
打赏作者
“感谢您的打赏,我会更努力的创作”
— 请选择您要打赏的金额 —
{{item.label}}
{{item.label}}
打赏成功!
“感谢您的打赏,我会更努力的创作”
返回

更多文章

更多精彩文章
扫一扫添加客服微信