C++11标准发布已有一段时间了, 维基百科上有对C++11新标准的变化和C++11新特性介绍的文章. 我是一名C++程序员,很是想了解一下C++11. 英文版的维基百科看起来很是费劲,而中文版维基百科不是知道是台湾仍是香港人翻译的而后由工具转换成简体中文的,有些术语和语言习惯和大陆程序不同! 我决定结合这两个版本按照我本身的习惯把这篇文章整理到个人博客中.分享给关注我和关注C++11的朋友们. 固然了, 本人水平有限,英语水平也很通常,就把这个过程当作学习C++11的过程吧.文章中确定会有不少错误或描述不恰当的地方. 很是但愿看到的朋友能给我指出来.html
如下是关于C++11的英文版本和中文版本维基百科的连接:java
http://en.wikipedia.org/wiki/C++11c++
http://zh.wikipedia.org/wiki/C++0x程序员
C++11,以前被称做C++0x,即ISO/IEC 14882:2011,是目前的C++编程语言的正式标准。它取代第二版标准ISO/IEC 14882:2003(初版ISO/IEC 14882:1998发布于1998年,第二版于2003年发布,分别通称C++98以及C++03,二者差别很小)。新的标准包含了几个核心语言增长的新特性,并且扩展C++标准程序库,并入了大部分的C++ Technical Report 1程序库(数学的特殊函数除外)。最新的消息被公布在 ISO C++ 委员会网站(英文)。算法
ISO/IEC JTC1/SC22/WG21 C++ 标准委员会计划在2010年8月以前完成对最终委员会草案的投票,以及于2011年3月召开的标准会议完成国际标准的最终草案。然而,WG21预期ISO将要花费六个月到一年的时间才能正式发布新的C++标准。为了可以如期完成,委员会决定致力于直至2006年为止的提案,忽略新的提案。最终,于2011年8月12日公布,并于2011年9月出版。2012年2月28日的国际标准草案(N3376)是最接近于现行标准的草案,差别仅有编辑上的修正。express
像C++这样的编程语言,经过一种演化的过程来发展其定义。这个过程不可避免地将引起与现有代码的兼容问题。不过根据Bjarne Stroustrup(C++的创始人,标准委员会的一员)表示,新的标准将几乎100%兼容现有标准。编程
C++的修订范围包括核心语言以及标准程序库。在开发2011版标准的各个特性的过程当中,标准委员会坚持如下指导思想:
* 维持与C++98,可能的话还有C之间的兼容性与稳定性;
* 尽量经过经过标准程序库来引进新的特性, 而不是扩展核心语言;
* 可以促进编程技术的变动优先;
* 改进 C++ 以帮助系统和程序库的设计,而不是引进只对特定应用有用的新特性;
* 加强类型安全,给现行不安全的技术提供更安全的替代方案;
* 加强直接与硬件协同工做的性能和能力;
* 为现实世界中的问题提供适当的解决方案;
* 实行零负担原则(若是某些功能要求的额外支持,那么只有在该功能被用到时这些额外的支持才被用到);
* 使C++易于教学promise
注重对初学者的关注,由于他们构成了计算机程序员的主体。也由于许多初学者不肯扩展他们的C++知识,他们仅限于掌握C++中本身所专精的部分.
C++标准委员会的一个职责是开发语言核心. 核心语言被大幅改进的领域包括: 多线程支持, 泛型编程支持, 统一的初始化和提升性能.
这篇文章将核心语言的特性和变化大体分为: 提升运行期性能, 提升编译期性能, 加强可用性, 和新特性4大类. 某些特性能够被划分到多个分类中, 但只会在主要体现该特性的分类中讨论一次.
3.提升核心语言运行性能
如下特性主要是为了提升性能提或计算速度等设计的.
3.1 右值引用和移动构造
在C++03及以前的标准中,临时对象(称为右值"R-values",由于一般位于赋值运算符的右边)的值是不能改变的,和C语言同样, 且没法和 const T& 类型作出区分。尽管在某些状况下临时对象的确会被改变,甚至有时还被视为是一个有用的漏洞。C++11新增长了一个很是量引用(non-const reference)类型,称做右值引用(R-value reference),标记为T &&。右值引用所引用的临时对象能够在该临时对象被初始化以后作修改,这是为了容许 move 语义。
C++03 性能上被长期诟病的问题之一,就是其耗时且没必要要的深拷贝。深拷贝会隐式地发生在以传值的方式传递对象的时候。例如,std::vector内部封装了一个C风格的数组和其元素个数,若是建立或是从函数返回一个std::vector的临时对象,要将其保存起来只能经过生成新的std::vector对象而且把该临时对象全部的数据复制进去。该临时对象和其拥有的內存会被销毁。(为简单起见,这里忽略了编译器的返回值优化)
在 C++11中,std::vector有一个"移动构造函数",对某个vector的右值引用能够单纯地从右值复制其内部C风格数组的指针到新的vector中,而后将右值中的指针置空。由于这个临时对象不会再被使用,没代码会再访问这个空指针, 并且由于这个临时对象的内部指针是NULL,因此当这个临时对象离开做用域时它的内存也不会被释放掉.因此,这个操做不只没有代价高昂的深拷贝, 仍是安全的,对用户不可见的!
这个操做不须要数组的复制,并且空的临时对象的析构也不会销毁内存。返回vector临时对象的函数只须要返回std::vector<T>&&。若是vector没有move 构造函数,那么就会调用常规拷贝构造函数。若是有,那么就会优先调用move构造函数,这可以避免大量的内存分配和内存拷贝操做。
右值引用不用对标准库以外的代码作任何改动就能够为已有代码带来性能上的提高. 返回值类型为std::vector<T>的函数返回了一个std::vector<T>类型的临时对象,为了使用移动构造不须要显示地将返回值类型改成std::vector<T>&&, 由于这样的临时对象会被自动看成右值引用. 可是在c++03中, std::vector<T>没有移动构造函数, 带有const std::vector<T>& 参数的拷贝构造会被调用, 这会致使大量内存分配和拷贝动做.
出于安全考虑, 须要施加一些限制! 一个已命名的变量即便声明为右值,也不会被视为右值.想要获取一个右值,应该使用模板函数std::move<T>(). 右值引用也能够在特定状况下被修改, 主要是为了与移动构造函数一块儿使用!
因为"右值引用"这个词的天然语义,以及对"左值引用"(常规引用)这个词的修正, 右值引用可让开发者提供完美的函数转发! 与可变参数模板结合时, 这个能力让模板函数可以完美地将参数转发给带有这些参数的另外一个函数.这对构造函数的参数转发最为有用,建立一个可以根据特定的参数自动调用适当的构造函数的工厂函数.
C++一直以来都有常量表达式的概念.这种表达式就像3+4这种在编译期和运行时都能获得相同结果的表达式. 常量表达式给编译器提供了优化的机会, 编译器计算出他们的值并把结果硬编码到程序中. 而且C++规格文档中有不少地方要求使用常量表达式. 例如,定义一个数组须要常量表达式(来指定数组大小), 枚举值必须是常量表达式.
然而,常量表达式中历来都不容许调用函数或建立对象. 因此,像下面这样的简单代码倒是非法的:
int get_five() {return 5;} int some_value[get_five() + 7]; // 建立一个包含12个整数的数组. 这种形式在C++中是非法的.
这在C++03中是非法的, 由于get_five() + 7不是常量表达式. C++03的编译器在编译期没办法知道get_five()是常量.由于从理论上讲, 这个函数能够影响(改变)一个全局变量或调用其它非运行时常量函数等.
C++11引入了constexpr关键字, 容许用户去保证一个函数或对象的构造函数是一个编译期常量.上面的例子能够写成下面这样:
constexpr int get_five() {return 5;} int some_value[get_five() + 7]; // 建立一个包含12个整数的数组. 这种形式在C++11中是合法的.
这样可让编译器理解并验证get_five()是一个编译期常量!做用在函数上的constexpr关键字对函数的行为施加了一些限制. 首先, 这个函数的返回值类型不能是void; 其次, 在函数体中不能声明变量或新类型; 第三, 函数体内只能包含声明语句,空语句和单个return语句且,return语句中的表达式也必须是常量表达式.
在c++11以前, 变量的值只有在变量被声明为const类型,有常量表达式初始化它, 而且是整型或枚举类型时才能用在常量表达式中. C++11去掉必须是整数或枚举的限制,若是定义变量时用了constexpr关键字:
constexpr double earth_gravitational_acceleration = 9.8; constexpr double moon_gravitational_acceleration = earth_gravitational_acceleration / 6.0;
这种数据变量是隐式的常量, 必须用常量表达式初始化.想要构造用户定义类型的常量值,构造函数也必须用constexpr声明.
3.3 对POD定义的修正 在C++03中, 类或结构体必须遵照几条规则才能认为是POD类型.符合这种定义的类型可以产生与C兼容的对象(内存)布局, 并且能够被静态初始化.C++03标准对与C兼容或能够静态初始化的类型有严格的限制,尽管不是因技术缘由致使编译器不接受这样的编码.若是建立了一个C++03的POD类型,又想要增长一个非虚成员函数, 那么这个类型就再也不是POD类型了, 不能静态初始化,而且与C不兼容了, 尽管没有改变内存布局.
C++11将POD的概念拆分红了两个独立的概念:平凡的(trivial)和标准布局(standard-layout), 放宽了几条POD规则. 一个平凡的(trivial)类型能够静态初始化. 这意味着能够经过memcpy来拷贝数据,而没必要经过拷贝构造函数. 平凡类型的变量的生命期是从分配存储空间开始的,而不是从构造完成开始.
平凡的类和结构体定义以下:
1 有一个平凡的默认构造函数. 这可使用默认构造函数语法, 例如SomeConstructor() = default;
2 有平凡的拷贝和移动构造函数, 可使用默认语法.
3 有平凡的拷贝和移动赋值运算符, 可使用转变语法.
4 有一个平凡的析构函数, 必须是非虚函数.
只有当一个类没有虚函数,没虚基类时它的构造函数才是平凡的. 拷贝和移动操做还要求类的全部非静态数据成员都是平凡的.
一个类型是标准布局的,就意味着它将以与C兼容的方式来排列和打包它的成员.标准布局的类和结构体定义以下:
1 没有虚函数
2 没有虚基类
3 全部的非静态数据成员都有相同的访问控制 (public, private, protected)
4 全部的非静态数据成员, 包括基类中的, 都要在继承体系中的同一个类中.
5 以上规则也适用于类体系中的全部基类和全部非静态数据成员.
6 没有和第一个定义的静态数据成员相同类型的基类
若是一个类型是平凡的(trivial),是标准布局的, 而且全部的非静态数据成员和基类都是POD类型的, 那么这个类型就是POD类型.
4.1 外部模板 在C++03中,只要在编译单元内遇到被完整定义的模板,编译器都必须将其实例化(instantiate)。这会大大增长编译时间,特别是模板在许多编译单元内使用相同的参数实例化。没有办法告诉C++不要引起模板的实例化.C++11引入了外部模板声明, 就像外部数据声明同样. C++03用下面的语法迫使编译器实例化一个模板:
template class std::vector<MyClass>;
而C++11提供下面的语法告诉编译器在当前编译单元中不要实例化这个模板:
extern template class std::vector<MyClass>;
这些特性存在的主要目的是为了让C++更使用. 这些特性能够改进类型安全, 最小化代码重复, 尽量减小错误代码等.
C++03从C语言继承了初始化列表这一特性. 在一对大括号中列出参数的方式来给出一个结构体或者数组, 这些参数值按照各个成员在结构体中的定义顺序来排列.这些初始化列表是递归的,因此一个结构体数组或包含另外一个结构体的结构体可使用它们.
这对于静态初列表或者只想把结构体初始化为某个特定值而言是很是有用的. C++提供了构造函数来初始化对象, 但这没有初始化列表方便.C++03只容许符合POD定义的类型使用初始化列表,非POD的类型不能使用,就连至关有用的STL容器std::vector也不行.C++11扩展了初始化列表, 使用它能够用在全部类上,包括像vector这样的标准容器.
C++11把初始化列表的概念绑到一个叫作std::initializer_list的模板上.这容许构造函数或其余函数将初始化列表作为参数.例如:
这使得能够从一串整数来建立SequenceClass对象, 例如:
SequenceClass some_var = {1, 4, 5, 6};
这个构造函数是一种特殊的构造函数,叫作初始化列表构造函数(initializer-list-constructor).有这种构造函数的类在统一初始化中会被特殊对待(详见5.2)
类型std::initializer_list<>是个第一级的C++11标准程序库类型. 可是,它们只能由C++11经过{}语法来静态构造!这个列表一经构造即可复制,虽然这只是copy-by-reference.初始化列表是常数;一旦被建立,其成员均不能被改变,成员中的数据也不可以被改变.
由于初始化列表是真实类型,除了构造函数以外还可以被用在其余地方。常规的函数可以使用初始化列表做为参数。例如:
C+03在初始化类型方面有着许多问题.初始化类型有数种方法,并且交换使用时不会都产生相同结果。传统的建构式语法,看起来像是函数声明,并且必须采起一些步骤保证不破坏编译器那些最让人恼火的解析规则.只有聚合体和POD类型可以用集合式初始化(经过SomeType var = {}; 形式的语法)
C++11提供了一个彻底统一的能够用在任何类型的对象的初始化语法. 它扩展了初始化列表语法:
var1初始化行为就像聚合初始化同样.也就是说,每一个数据成员就是一个对对象, 按顺序从初始化列表中拷贝一个对应的值来初始化它们.若是有须要, 会进行隐式类型转换.若是存在向下类型转换(转换后的数据类型不能表示原数据类型,转换后可能有数据丢失,例如将unsigned转换成int), 那么这个程序就是病态的,会致使编译失败. var2的初始化则是简单地调用构造函数.
统一的初始化还可作下面这件事:
统一初始化不会取代构造函数语法,仍是有一些时候是须要构造函数语法的.若是一个类有初始化列表构造函数(TypeName(initializer_list);),假定它有资格成为构造函数之一(咱们知道,一个类能够有多个构造函数),那么它的优先级会高于其它形式的构造函数.C++11版本的std::vector就有一个初始化列表构造函数.这意味着 std::vector the_vec{4};会调用初始化列表构造函数,而不是调用以vector大小为惟一参数的构造函数. 要访问后一个构造函数, 用户必须直接使用标准构造函数语法.
在C++03(还有C)中,必须显式指定变量的类型.然而,随着模板类型和模板元编程技术的出现,某些东西的类型,尤为是函数的返回类型,可能不是那么容易表示的了. 在这种状况下,将中间结果存储在某个变量中是件很困难的事情.可能须要去了解特定的模板元编程库的内部实现.
C++11提供两种方法来缓解上述问题. 一,定义有显式初始化的变量能够用auto关键字来自动肯定变量类型,这将用初始化表达式的类型来建立变量:
some_strange_callable_type的类型很简单, 就是boost::bind模板函数返回值的类型.做为编译器语义分析责任的一部份,编译很容易肯定这个类型,但程序员就没那么容易肯定了.otherVariable 的类型一样也是定义明确的,程序员很容易就能判别。它是个int(整数),就和整数字面值的类型同样。
另外,关键字decltype能够用来在编译期肯定表达式的类型.例如:
decltype 和 auto 一块儿使用会更为有用,由于 auto 参数的类型只有编译器知道.然而 decltype对于那些大量运用运算符重载和类型特化来编码的表达式很是有用。auto对减小代码冗余也颇有用.好比说, 程序员不用像下面这样写代码:
这两种形式的差距会随着你使用的容器的嵌套层次而增长, 这种状况下typedef也是一种减小代码的好方法!由decltype得出的类型能够和由auto推导出的类型不一样:
在C++03中,要遍历一个list中的元素须要不少代码.其它语言实现支持"糖块句法",容许程序经过一个简单的"foreach"语句自动遍历list中的元素.其中之一就是Java语言, 它从5.0开始支持加强的for循环.
C++11增长了一个相似的特性, for语句能够简单地遍历列表中的元素.
这种形式的for语句叫做"基于范围的for语句",它会遍历列表中的每个元素.能够用在C风格数组,初始化列表和那些带有能返回迭代器的begin()和end()函数的类型上.全部提供了begin/end的标准容器均可以使用基于范围的for语句.
C++11提供了建立匿名函数的能力,叫作Lamda函数. 具体内容请参考: http://www.cnblogs.com/pzhfei/archive/2013/01/14/lambda_expression.html
标准C的函数声明语法对C语言的特性集而言彻底足够了. 由于C++从C发展而来, 保留了C的基本语法并在须要的地方进行了扩展. 然而,C++的结构变得更加复杂了,暴露出了不少的局限性,尤为是模板函数的声明.下面的例子在C++03中是不容许的:
Ret的类型是lhs+rhs的结果的类型.就算用前面提到的C++11中的decltype,也是不行的:
这不是合法的C++,由于lhs和rhs还没定义;解析器解析完函数原型的剩余部分以前,它们还不是有效的标识符.
为此, C++11引入了一种新的函数声明语法,叫作后置返回类型(trailing-return-type).
这种语法能够用到更普通的函数声明和定义上:
关键字auto的这种用法与在自动类型推导中有所不一样.
C++03中类的构造函数不容许调用该类的其它构造函数;每一个构造函数都必须本身或者调用一个公共的成员函数来构造类的所有成员.例如:
并且,基类的构造函数不能直接暴露给派生类;每一个派生类必须实现本身的构造函数哪怕基类的构造函数已经够用了.非静态数据成员不能在声明的地方初始化.它们只能在构造函数中初始化.
C++11为这些问题提供了解决方案.C++11容许构造函数调用另外一个构造函数(叫作委托构造).这容许构造函数利用其它构造函数的行为而只需增长少许的代码.C#,java和D语言都提供了这种功能. C++的语法以下:
注意:这个例子能够经过给new_number设定一个默认参数来达到相同的效果.可是,这种新语法可让这个默认值在实现中来设置而不是在接口中设置.这带来的一个好处就是,对库代码的维护者而言,在接口中(头文件中)声明默认值,这个默认值被嵌入到了调用端;要改变这个默认值的话,调用端的代码都须要从新编译.但委托构造能够在实现中(CPP文件中)来改变这个默认值, 这样调用端的代码就不须要从新编译,只用从新编译这个库就能够了.
还有一个附加说明: C++03认为,构造函数执行完了一个对象就被构造好了. 而C++11则认为,只要任何一个构造函数执行完了,对象就算构造完成了. 因为可能有多构造函数会被执行,C++11的这种作法就意味着,全部的委托构造函数都将在一个已经用它本身类型彻底构造好的对象上执行.这句话什么意思呢?举个例子, B 继承自 A, B的构造函数里调用了A的构造函数;当A的构造函数执行完之后,就已经有一个A类的对象构造完成了.而这时B的构造函数不会再构造一个新对象,而是把那个A对象改形成B类的对象(这是个人推测).再举一个例子,类C有两个构造函数C1和C2, C2调用了C1. 当C1执行完后,已构造好了一个C类对象.而这时C2的代码会直接做用在这个对象上,不会再构造一个新对象.C++03就会构造2个对象,其中一个是临时对象.
对于基类的构造函数,C++11容许一个类指定要不要继承基类的构造函数.注意,这是一个"所有"或"全不"的特性,要么继承基类的所有构造函数,要么一个都不继承.
此外,对多重继承有一些限制,从多个基类继承而来的构造函数不能够有相同的函数签名(signature).而派生类的新加入的构造函数也不能够和继承而来的基类构造函数有相同的函数签名,由于这至关于重复声明.语法以下:
每个构造函数都将把value初始化为5, 若是它们没用其它值来覆盖这个初始化的话.上面那个空的构造函数会把value初始化为类定义时的状态5.而那带有参数的构造函数会用指定的值来初始化value.成员的初始化也可使用前面提到的统一初始化.
在C++03中,很容易让你在本想重写基类某个函数的时候却意外地建立了另外一个虚函数.例如:
原本Derived::some_func函数是想替代Base中那个函数的.可是因它的接口不一样, 又建立了一个虚函数.这是个常见的问题, 特别是当用户想要修改基类的时候.
C++11引入了新的语法来解决这个问题:
override 这个特殊的标识符意味编译器将去检查基类中有没有一个具备相同签名的虚函数,若是没有,编译器就会报错!
C++11还增长了防止基类被继承和防止子类重写函数的能力.这是由特殊的标识符final来完成的,例如:
在这个例子中, virtual void f() final;语句声明了一个虚函数却也阻止了子类重写这个函数.它还有一个做用,就是防止了子类将那个特殊的函数名与新的参数组合在一块儿.
须要注意的是,override和final都不是C++语言的关键字.他们是技术上的标识符,只有在它们被用在上面这些特定的上下文在才有特殊意义.用在其它地方他们仍然是有效标识符.
本节中出的"0"都将解释为"一个求值结果为0的int型常量表达式". 实际上任何整数类型均可以做为常量表达式.
自从1972年C诞生以来,常量0就有着int型常量和空指针的双重角色.C语言用预处理宏NULL来处理这个固有的歧义, NULL一般被定义为(void*)0或0.而C++不采用一样行为,只容许0作空指针常量.而这与函数重载配合时就显得有些弱智了.
若是NULL定义为0,那么foo(NULL);语句将会调用foo(int).这几乎一定不是程序员想要的,也不是代码直观上要表达的意图.
C++11经过引入一个新的关键字nullptr充当单独的空指针常量来纠正这个问题.它的类型是nullptr_t,是一个能够隐式转换任意类型的指针或指向成员的指针的类型,而且能够和这些类型进行比较.它不能隐式转换为整型,也不能与整型作比较,bool类型除外.尽管最初的提议中一个nullptr类型的右值不该该能转换为bool类型,可是为了保持与常规指针类型的一致性,核心语言工做组仍是认定这种转换是合理的. 为了向下兼容,0仍然是一个有效的空指针常量!
在C++03中,枚举不是类型安全的.他们其实是整数,尽管他们是不一样的枚举类型.这使得咱们能够比较两种不一样类型的枚举值.C++03提供的惟一安全性就是,一个整数或一个枚举类型的值不能隐式地转换成另外一个枚举类型.此外,底层的具体的整数类型(short,long,int,...)是由实现(编译器)定义的,标准并没有明确规定.所以,那些枚举变量的大小的代码将是不可移植的.最后,枚举值是暴露在外层做用域(直接包含枚举定义的做用域)中的.因此,两个不一样枚举类型的成员不可能有相同的名字.
C++11引入了一个没上述问题的特殊"枚举类".使用 enum class(也能够用同义词enum struct)来声明:
这种枚举是类型安全的;枚举值不能隐式地转换成整数,因此也不能够和整数作比较.表达式 Enumeration::Val4 == 101会报一个编译错误.
枚举类的底层类型老是已知的.默认是int型,这能够用其它整数类型来覆盖它.就像下面这个例子:
老式的枚举被放在直接包含该定义的做用域中.新式的枚举被放在枚举类的做用中.因此,上例中Val1是未定义的,而Enum2::Val1是已定义的.
C++11还提供了一个过渡语法让老式的枚举类型能够提供显式的做用域和定义底层整数类型.语法以下:
这个例子中枚举名字被定义在枚举类型的做用域内(Enum3::Val1),可是为了向下兼容它们也会被放在直接包含在Enum3所在的做用域中.
C++03的解析器都把">>"定义为右移运算符.可是,在嵌套的模板声明中,程序员每每倾向于忽略两个右尖括号之间的空格.这会致使编译器报一个语法错误.
C++11改进了编译器的解析规则,尽量地将多个右尖括号(>)解析成模板参数结束符.能够用圆括号来改变这个规则,圆号的优先级比它高.例如:
C++98 增长了explicit关键字来防止单参数的构造函数被用做隐式的类型转换操做符.然而,却没有对真正的类型转换操做符这样作.例如,智能指针可能定义了operator bool(),让它的行为更像真原始指针.有了这个转换操做符就能够用if语句来测试:if (smart_ptr_variable),当这个指针不为空时结果为真,不然为假.可是,这也会引发其余一些非预期的转换操做.因C++中的bool被定义为一种算术类型,能够隐式地转换为整数甚至是浮点数进行数学运算. 拿转换出的布尔值进行非布尔计算的数学计算,每每不是程序员想要的.
C++11中,explicit关键字也能够用在类型转换操做符上.和用在构造函数上同样,防止这种转换函数被于隐式类型转换.固然了,在语言的上下文明确要求使用布尔变量(如if语句和循环语句中的条件,还有逻辑运算符的操做数)的地方,会被看成显示转换.因此可使用类型转换操做符.
在进入这个主题前,先弄清楚"模板"和"类型"的区别.类型,是具体的数据类型,能够直接用来定义变量. 模板,是类型的模板,根据这个模板能够产生具体的类型;模板是不能直接定义变量的;当指定了全部的模板参数后,就产生了一个具体的类型,就能够用来定义变量了.
在C++03中,只能为类型(包括彻底特化的模板,也是一种类型)定义别名, 而不能为模板定义别名:
C++11增长为模板定义别名的能力,用下面这样的语法:
这种using语法也能够用来定义类型的别名:
C++03中,对哪些类型的对象可以做为联合的成员是有限制的.例如,联合不能包含定义了非平凡构造函数的对象.C++11废除了其中的一些限制:
如今,联合能够包含定义了非平凡构造函数的对象;若是包含了,那么联合就必需要显式定义一个构造函数.
由于是放宽了现有的规则,因此不会对已有的代码形成影响.
这些特性让C++语言能够完成那些之前不可能的,极其繁琐的或者须要一些不可移植的库才能完成的事情.
在 C++11 以前, 不管是类模板或是函数模板,都只能按其被声明时所指定的样子,接受一组数目固定的模板参数.C++11 加入新的表示法,容许任意个数,任意类型的模板参数,没必要在定义时将参数的个数固定。
模板类 tuple 的对象,能接受不限个数的 typename 做为它的模板形参:
实参的个数也能够是0,因此 class tuple<> someInstanceName 这样的定义也是能够的。
若不但愿产生实参个数为 0 的变长参数模板,则能够采用如下的定义:
变长参数模板也能运用到函数模板上。传统 C 中的 printf 函数,虽然也能作到不定个数的形参来调用,但其并不是类型安全的。如下的例子中,C++11 除了能定义类型安全的变长参数函数外,还能让相似 printf 的函数能天然地处理自定义类型的对象。 除了在模板参数中能使用...表示不定长模板参数外,函数参数也使用一样的表示法表明不定长参数。
其中,Params 与 parameters 分别表明模板与函数的变长参数集合,称之为参数包 (parameter pack).参数包必需要和运算符"..."搭配使用,避免语法上的歧义。
变长参数模板中,没法像在类或函数中那样使用参数包.所以典型的作法是以递归的方法取出可用参数,请看如下的 C++11 printf 例子:
printf 会不断地递归调用自身:函数参数包 args... 在调用时, 会被模板类匹配分离为 T value和 Args... args.直到 args... 变为空参数,则会与简单的 printf(const char *s) 造成匹配,退出递归。
另外一个例子为计算模板参数的个数,这里使用类似的技巧展开模板参数包 Args...:
虽然没有一个简洁的机制可以对变长参数模板中的值进行迭代,但使用运算符"..."还能在代码各处对参数包施以更复杂的展开操做。举例来讲,一个模板类的定义以下:
BaseClasses... 会被展开成类型 ClassName 的基底类; ClassName 的构造函数须要全部基类的左值引用,而每个基类都是以传入的参数作初始化 (BaseClasses(baseClasses)...)。
在函数模板中,变长参数能够和左值引用搭配,达成形参的完美转发 (perfect forwarding):
参数包 parms 可展开为 TypeToConstruct 构造函数的形参。 表达式std::forward<Args>(params) 可将形参的类别信息保留(利用右值引用),传入构造函数。 而运算符"..."则能将前述的表达式套用到每个参数包中的参数。这种工厂函数(factory function)的手法, 使用 std::shared_ptr 管理配置对象的内存,避免了不当使用所产生的内存泄漏(memory leaks)。
此外,变长参数的数量能够藉如下的语法得知:
SomeStruct<Type1, Type2>::size 是 2,而 SomeStruct<>::size 会是 0。 (sizeof...(Args) 的结果是编译期常数。)
C++03提供两种字符串字面值.第一种,包含在一对双引号内,产生一个以空字符结尾的const char数组.第二种,由L""定义,产生以空字符结尾的const wchar_t类型的数组.wchar_t是一个大小和定义都未明肯定义的宽字符.字符串字面值既不支持UTF-8,UTF-16也不支持其它任何类型的unicode编码.
char类型的定义被修改了,明确表述为:char的大小至少能存储UTF-8的8位编码,而且要足够大到可以存储编译器实际使用的字符集的任何成员.这之前只在C++标准的后半部分中有定义,依靠C标准来保证char的大小至少为8位.
C++11支持3种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编码值到字符串中.为此,C++11提供以下语法:
u8"This is a Unicode Character: \u2018." u"This is a bigger Unicode Character: \u2018." U"This is a Unicode Character: \U00002018."
'\u'后面是一个16进制数字,不须要加0x前缀.标识符\u表明一个16位的Unicode码点.要输入32位的码点,使用\U加上32位的16进制数.只能输入有效的Unicode码点.例如,U+D800—U+DFFF以前的码点是被禁止的,由于他们被保留用做UTF-16编码中的代理对.
有时候咱们须要手动避免转义某些字符,尤为是在使用xml文件,脚本语言或正则表达式等的字符串字面值时.C++提供了原始字符串:
R"(The String Data \ Stuff " )" R"delimiter(The String Data \ Stuff " )delimiter"
C++03提供几种字面值.字符串"12.5"会被编译器解析为double类型的值12.5. 可是,带有'f'后缀的字符串"12.5f"会建立一个float类型的值12.5. 后缀修饰符已经被C++标准固定下来了, 用户代码不能增长新的修饰符!
C++11增长了让用户定义新的字面值修饰符的能力, 新的修饰符会基于被修饰的字符串来构造对象.
字面值的转换能够分为两个阶段:原始的和转换后的(raw and cooked).原始的字面值是指某种特定类型的字符序列, 而加工过的字面值则表明另外一种类型.C++的字面值1234, 原始的字面值就是字符序列'1','2','3','4';而转换后的字面值是整数1234. C++字面值0xA,转换前的字面值是'0','x','A',转换后就是整数10.字面值原始和转换后的形式均可以被扩展.但字符串除外,它只有转换后的形式能够被扩展.这个例外是由于考虑到字符串有着会影响字符的特定意义和类型的前缀. 全部的用户自定义字面值都是加后缀的, 想定义加前缀的字面值是不可能的.
自定义字面值原始形式的处理定义以下:
OutputType operator "" _suffix(const char * literal_string); OutputType some_variable = 1234_suffix;
第二个语句执行由自定义字面值定义的函数代码.
另外一种处理整数和浮点数原始字面值的机制是经过可变参数模板:
template<char...> OutputType operator "" _tuffix(); OutputType some_variable = 1234_tuffix; OutputType another_variable = 2.17_tuffix;
这个例子展现了如何以operator "" _tuffix<'1', '2', '3', '4'>()函数处理字面值.这种形式中,字符串没有结尾的空字符!这么作的主要目的是为了使用C++11的constexpr关键字和编译器能在编译期就彻底转换这些字面值.
对于数字字面值,整数字面值转换后的类型是unsigned long long,浮点数字面转换后的类型是long double.没有模板形式:
OutputType operator "" _suffix(unsigned long long); OutputType operator "" _suffix(long double); OutputType some_variable = 1234_suffix; // Uses the 'unsigned long long' overload. OutputType another_variable = 3.1416_suffix; // Uses the 'long double' overload.
对于字符串字面值,下面的例子用了前面提到的新的字符串后缀.
OutputType operator "" _ssuffix(const char * string_values, size_t num_chars); OutputType operator "" _ssuffix(const wchar_t * string_values, size_t num_chars); OutputType operator "" _ssuffix(const char16_t * string_values, size_t num_chars); OutputType operator "" _ssuffix(const char32_t * string_values, size_t num_chars); OutputType some_variable = "1234"_ssuffix; // Uses the 'const char *' overload. OutputType some_variable = u8"1234"_ssuffix; // Uses the 'const char *' overload. OutputType some_variable = L"1234"_ssuffix; // Uses the 'const wchar_t *' overload. OutputType some_variable = u"1234"_ssuffix; // Uses the 'const char16_t *' overload. OutputType some_variable = U"1234"_ssuffix; // Uses the 'const char32_t *' overload.
内存模型容许编译器完成很重要的优化.即便像移动程序中的语句来合并循环这样简单的编译器优化都可以影响对潜在共享变量读,写操做的顺序!改变读写顺序会致使竞态条件的产生.没有内存模型,编译器通常不能将这种优化应用到多线程程序中的,或者只能用于某些特殊状况.现代程序设计语言,好比Java,为此实现了一个内存模型.内存模型指定了同步屏障(Synchronization Barriers),经过特殊的、定义好的同步操做(好比得到一个进入同步块或某方法的锁)来创建的.内存模型规定,共享变量值的改变只须要对那些经过了同步屏障的线程是可见的.此外,竞态条件这个概念的完整定义覆盖了带有内存屏障细节的操做顺序. 这些语义给了编译器更高的自由度去进行优化: 编译器只须要确保优化前和优化后同步屏障内的变量(可能被共享)的值是同样的.
大多数关于内存模型的研究都是围绕着如下主题进行的:
设计一个能让编译器有最大的优化自由度,同时还能对自由竞争提供足够保障的内存模型;
提供关于这种内存模型的正确的程序优化.
C++11标准支持多线程编程.这包含两个部分:同一个程序中容许有多个线程同时存在和库支持线程之间的交互;内存模型定义了何时多个线程可以访问同一个内存地址,并指定了何时一个线程对内存的修改对另外一个线程是可见的!(参见:7.2 线程设施).
在多线程环境中,线程一般都有一些本身所独有的变量. 函数的局部变量也是这样, 可是全局变量和静态变量就不同了.
新的线程局部存储的生存期(原有的静态,动态,自动变量除外)由thread_local关键字指定. 静态对象的生存期也可能会被thread-local生存期替代.这么作的目的是让thread-local(线程局部)生存期的对象能够像其余静态对象同样由构造函数建立,由析构函数销毁.
C++03中,若是类没有定义构造函数,拷贝构造函数,赋值函数和析构函数的话,编译器会为类提供这些函数.程序员能够本身定义这些函数来覆盖编译生成的默认版本.C++还定义了几个能够做用在全部类上的操做符(好比,赋值操做符=,new操做符等),程序员也能够覆盖它们.
然而, 对这些默认函数的建立只有不多的控制.例如, 要生成一个不可拷贝的类必需要声明私有的拷贝构造函数和私有的赋值操做符而且不定义它们的实现.试图调用这些函数就会违反"一个定义原则"(ODR,一个函数能够被调用,那么这个函数必须且只能有一个函数体定义).尽管诊断信息不是必须的,可是这类违规行为可能会致使连接错误.
就构造函数而言, 只要一个类定义了任意一个构造函数,编译器就不会自动为它生成构造函数了.这在不少状况下是颇有用的,但有些状况下用户定义了这些函数,编译器还生成这些函数也是颇有用的.
C++11容许显式指明要不要使用这些特殊的成员函数.例如,下面的声明显式指出要使用默认构造函数:
struct SomeType { SomeType() = default; //The default constructor is explicitly stated. SomeType(OtherType value); };
另外一方面,一些特性能够被显式地禁用.例如,下面的类是不可拷贝的:
struct NonCopyable { NonCopyable() = default; NonCopyable(const NonCopyable&) = delete; NonCopyable & operator=(const NonCopyable&) = delete; };
指示符 = delete 能够用来阻止任何函数被调用,能够用来禁止调用带特定参数的成员函数.例如:
struct NoInt { void f(double i); void f(int) = delete; // 不能调用这个函数 };
编译器会拒绝试图对带int参数的函数f()的调用, 而不是默默地转换为对带有double参数的f()的调用.这能够泛化到禁止除了带double参数外其余任何参数类型的f()的调用.例如:
struct OnlyDouble { void f(double d); template<class T> void f(T) = delete; //不能调用这个函数 };
C++03中,最大的整数类型是long int.它保证使用的位数至少与int同样. 这致使long int在一些实现是64位的, 而在另外一些实现上倒是32位的.C++11增长了一个新的整数类型long long int来弥补这个缺陷.它保证至少与long int同样大,而且很多于64位.这个类型早在C99就引入到了标准C中, 并且大多数C++编译器都以扩展的形式支持这种类型了.
C++03提供两种方法来测试断言:宏assert和#error预处理指令.然而,这不适合用在模板中:宏在运行期间测试断言,而预处理指令在编译预处理阶段测试断言,这些都发生在模板实例化以前;也不适合用于依赖于模板参数的属性.
C++11引入了一个新的关键字static_assert在编译期测试断言.声明呈现下面这样的形式:
static_assert (constant-expression, error-message); //下面几个例子展现怎样使用static_assert: static_assert((GREEKPI > 3.14) && (GREEKPI < 3.15), "GREEKPI is inaccurate!"); template<class T> struct Check { static_assert(sizeof(int) <= sizeof(T), "T is not big enough!"); }; template<class Integral> Integral foo(Integral x, Integral y) { static_assert(std::is_integral<Integral>::value, "foo() parameter must be an integral type."); }
当常量表达的结果为false时,编译器就会产生一个错误消息.第一个例子相似于预处理指令#error,可是预处理指令只支持整数类型.相比之下,第二例子中的断言在模板类Check每一次被实例化的时候都被检查一次.
除了模板以外,静态断言也是颇有用的.例如:一个算法的某个实现依赖于long long类型必须大于int,这类型事情标准并无作出保证.这种假设在大多数系统和编译器上是有效的,但毫不是所有!
6.9 容许sizeof运算符做用在类型的数据成员上,无须明确的对象
C++03中,sizeof能够做用在类和对象上.但却不能像下面这样作:
struct SomeType { OtherType member; }; sizeof(SomeType::member); // C++03 不行. C++11 能够. //这会返回OtherType的大小.C++03不容许这样作,会报一个编译错误.C++11容许这样作.
C++11能够用alignof和alingas来查询和控制变量的对齐方式.
alignof是一个操做符,他以一个类型为参数,而且返回这个类型的实例必须分配的字节边界值,这个值必定是2的整数次幂.若是参数是引用类型,那么返回的是被引用的类型的对齐信息.对于数组,返回的是元素类型的对齐信息.
laignas指示符变量的内存控制方式.这个指示符的参数是一个常量或一个类型, alignas(T)是alignas(alignof(T))的简写形式.例如:下面的例子声明一个char数组,它的对齐方式与float型数据同样.
alignas(float) unsigned char c[sizeof(float)]
以前版本的C++标准经过set_new_handler提供了程序员驱动的垃圾回收机制,但却没有为自动化垃圾回收机制给出对象可到达性的定义. C++11定义了指针彻底地从其余地方得到值的条件.编译器实现能够指定在严格的指针安全下进行操做,在这种状况下不按这个规则得到值的指针就会变成无效的.
6.12 属性
C++11为编译器和其余工具提供了标准的语言扩展语法.这些扩展从来都是用#pragma指令或生产商指定的关键字(如GNU的__attributes__和微软的 __declspec).C++11有了新的语法, 以双重方括号的形式为属性指定额外的信息.属性能够被用于各类代码元素:
int [[attr1]] i [[attr2, attr3]]; [[attr4(arg1, arg2)]] if (cond) { [[vendor::attr5]] return i; }
在上面的例子中,属性attr1做用在变量i的类型int上,而attr2和attr3则做用于变量i自己.attr4做用于if语句,vendor::attr5做用于return语句.通常地(但有一些例外),为一个命名实体指定的属性放在实体名字以后,其余部分以前.多个属性能够放在一个双重方括号对中,像上面的例子那样. 属性可能会有附加的参数,属性也可能被放在生产商指定的属性命名空间中.
建议属性不要有任何语言上的意义,也不要改变程序的观感. 属性能够提供一些颇有用的信息,例如帮助编译器生成更好的诊断信息或优化生成的代码.
C++11自己提供两种标准的属性:noreturn属性指出函数没有返回值, carries_dependency属性经过指出函数的参数或返回值有依赖关系来帮助优化多线程代码.
C++11标准库引入了不少新特性.不少是在旧标准下实现的,可是有一些却依赖于C++11的核心特性.新标准库的大部分是在2005年公布的C++标准委员会标准库技术报告(tr1)中定义的.各类彻底或部分的TR1实如今现行的标准中能够经过命名空间std::tr1来引用了.对于C++11,这些实现被移到了命名空间std中.然而,由于TR1的特性被引入到了C++11的标准库中,因此须要更新它们以适合那些在最初的TR1中不可用的C++11特性.
C++11的标准化已经完成,标准委员会打算建立第二版标准库技术报告(TR2).那些被提议但却没来及加入C++11的库,将会被放入TR2或之后的技术报告中.
C++11提供了不少现存标准库组件能从中获益的新特性.例如,大多数标准容器均可以从基于移动构造右值引用中获益,无论是快速移动重型容器仍是把容器的内容移动到新的内存位置.标准库组件已经用适当的C++11新特性升级过了.包括但不限于如下特性:
C++11虽然从语言上提供了支持线程的内存模型,但主要的支持仍是来自标准库.
新的标准库提供了一个线程类(std::thread)来运行一个新线程,它带有一个函数对象参数和一系列可选的传递给函数对象的参数.经过std::thread::join()支持的线程链接操做可让一个线程直到另外一个线程执行完毕才中止.std:thread::native_handle()成员函数提供了对底层本地线程对象的可能且合理的平台相关的操做.
为支持线程同步,标准库增长了互斥体(std::mutex, std::recursive_mutex等)和条件变量(std::condition_variable 和std::condition_variable_any).这些都是经过RAII锁和加锁算法就能够简单使用的.
有时为了高性能或底层工做,要求线程间的通讯没有开销巨大的互斥锁.原子操做能够达到这个目的,这能够随意地为一个操做指定最小的内存可见度.显式的内存屏障也能够用于这个目的.
C++11线程库还包含了futures和promises,用于在线程间传递异步结果.而且提供了std::packaged_task来封装能够产生这种异步结果的函数调用.
更高级的线程支持,如线程池,已经决定留待在将来的 Technical Report 加入此类支持。更高级的线程支持不会是 C++11 的一部份,可是其最终实现将创建在目前已有的线程支持之上。std::async 提供了一个简便方法来运行线程,并将线程绑定在 std::future上。用户能够选择一个工做是要在多个线程上异步的运行,仍是在一个线程上运行并等待其所须要的数据。默认的状况,实现能够根据底层硬件选择前面两个选项的其中之一。另外在较简单的使用场景下,实现也能够利用线程池提供支持。
元组(tuple)由预先肯定数量的多种对象组成.元组能够看做是struct数据成员的泛化.TR1 tuple类型的C++11版本获益于像可变参数模板这样的C++11语言特性.TR1版本的元组须要一个由实现定义的包含的类型的最大数目,并且须要大量的宏技巧来实现.相比之下,C++11版本的不须要显式的实现定义的最大类型数目.尽管编译器有一个内部的模板实例化的最大递归深度,但C++11版的元组不会把它暴露给用户.
用可变参数模板,元组类的定义看上去像下面这样:
template <class ...Types> class tuple; //下面是定义和使用元组的一个例子: typedef std::tuple <int, double, long &, const char *> test_tuple; long lengthy = 12; test_tuple proof (18, 6.5, lengthy, "Ciao!");
lengthy = std::get<0>(proof); // 把'lengthy' 赋值为18.
std::get<3>(proof) = " Beautiful!"; // 修改元组的第四个元素
能够在定义一个元组时不定义他的内容,不过只有当它的每一个元素类型都有默认构造函数时才能够这样作.并且,能够将一个元组赋值给另外一个元组:若是两个元组的类型相同,且每种元素类型都有拷贝构造函数;不然,须要右边的元组的元素能够隐式转换成左边元组的对应元素类型或者左边元组的元素类型有合适的构造函数.
typedef std::tuple <int , double, string > tuple_1 t1; typedef std::tuple <char, short , const char * > tuple_2 t2 ('X', 2, "Hola!"); t1 = t2; // Ok, 前两个元素都是能够转换的. // 第三个能够用'const char *'来构造一个string对象.
能够对元组类型对象的进行比较运算(当它们拥有一样数量的元素)。此外,C++11 提供两个表达式用来检索元组类型的属性(仅在编译期作此检查)。
std::tuple_size<T>::value //返回元组 T 内的元素个数, std::tuple_element<I, T>::type //返回元组 T 内的第 I 个元素的类型
在过去,不断有要求想将散列表(无序关系式容器)引进标准库。只由于时间上的限制,散列表才没有被标准库所采纳。虽然,散列表在最糟状况下(若是出现许多冲突 (collision) 的话)在性能上比不过平衡树。但实际运用中,散列表的表现则较好。
由于标准委员会还看不到有任何机会能将开放寻址法标准化,因此目前冲突仅能经过线性链(linear chaining) 的方式来处理。为避免与第三方库发展的散列表发生名称上的冲突,前缀将采用 unordered 而非 hash。
标准库将引进四种散列表,其中差异在于如下两个特性: 是否接受具相同键值的项(Equivalent keys),以及是否会将键值映射到相对应的数据(Associated values).新的标准库增长了如下散列表类型:
散列表类型 | 有无关系值 | 接受相同键值 |
---|---|---|
std::unordered_set |
否 | 否 |
std::unordered_multiset |
否 | 是 |
std::unordered_map |
是 | 否 |
std::unordered_multimap |
是 | 是 |
这些类彻底具有容器类需的条件,同时也提供访问其中元素的成员函数: insert, erase, begin, end。
散列表不须要对现有核心语言作扩展(虽然散列表的实现会利用到 C++11 新的语言特性),只会对头文件 <functional> 作些许扩展,并引入 <unordered_set> 和 <unordered_map> 两个头文件。对于其它现有的类型不会有任何修改。同时,散列表也不会依赖标准库的其它扩展功能。
新的标准库定义了一个新的头文件<regex>,由一些新的类组成:
函数 regex_search 是用来搜索模式的; 若要搜索并替换,则要使用函数 regex_replace,该函数会返回一个新的字符串。算法regex_search 和 regex_replace 接受一个正则表达式(模式)和一个字符串,并将该模式匹配的结果状况存储在 struct match_results对象中。
下面的例子展现了 match_results 的用法:
const char *reg_esp = "[ ,.\\t\\n;:]"; // 列出分隔符. // 这也能够经过字符串字面值来完成: // const char *reg_esp = R"([ ,.\t\n;:])"; std::regex rgx(reg_esp); // 'regex' 是模板类'basic_regex'以'char'为类型参数特化的类. std::cmatch match; // 'cmatch'是模板类'match_results'以'const char *'特化的类. const char *target = "Unseen University - Ankh-Morpork"; // 找出'target'中全部以'reg_esp'中的字符分隔的单词. if (std::regex_search(target, match, rgx)) { // 若是找到了指定的单词 const size_t n = match.size(); for (size_t a = 0; a < n; a++) { std::string str (match[a].first, match[a].second); std::cout << str << "\n"; } }
注意双反斜杠的使用,由于 C++ 将反斜杠做为转义字符使用。但 C++11的原始字符串(raw string)能够用来避免这一问题。库 <regex> 不须要改动到现有的头文件,同时也不须要扩展示有的语言特性。
这些指针是由 TR1 智能指针演变而来。注意! 智能指针是类而非通常指针。shared_ptr 是引用计数型(reference-counted) 指针类,其行为与通常 C++ 指针极为类似。在 TR1 的实现中,缺乏了一些通常指针所拥有的特性,像是别名或是指针运算。C++11增长了这些特性。如下是一个使用 shared_ptr 的例子:
int main( ) { std::shared_ptr<double> p_first(new double) ; { std::shared_ptr<double> p_copy = p_first ; *p_copy = 21.2; } // 此時 'p_copy' 会被销毁,但动态分配的 double 不会被销毁。 return 0; // 此时'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 将会提供三种算法,每一种算法都有其强项和弱项:
样板类 | 整数/浮点数 | 品质 | 速度 | 状态数* |
---|---|---|---|---|
linear_congruential | 整数 | 低 | 中等 | 1 |
subtract_with_carry | 二者皆可 | 中等 | 快 | 25 |
mersenne_twister | 整数 | 佳 | 快 | 624 |
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_distribution<int> distribution(0, 99); // 创建分布,以离散均匀分布方式在0到99之间产生随机数 std::mt19937 engine; // 创建随机数生成引擎 auto generator = std::bind(distribution, engine); // 利用 bind 随机数生成引擎和分布組合成一个随机数生成器 int random = generator(); // 产生随机数
咱们能够经过实例化模板类 reference_wrapper 获得一个封装引用 (wrapper reference)。封装引用相似于通常的引用。对于任意对象,咱们能够经过模板类 ref 获得一个封装引用 (至于 constant reference 则可经过 cref 获得)。当模板函数须要形参的引用而非其拷贝时封装引用就能派上用场了:
// 此函数将获得r的引用,并将r的值加1. void f (int &r) { r++; } // 模板函数 template<class F, class P> void g (F f, P t) { f(t); } int main() { int i = 0 ; g (f, i) ; // 实例化 'g<void (int &r), int>' // 'i' 不会被修改 std::cout << i << std::endl; // 输出 0 g (f, std::ref(i)); // 实例化 'g<void(int &r),reference_wrapper<int>>' // 'i' 会被修改 std::cout << i << std::endl; // 输出 1 }
这项功能将加入头文件 <utility> 之中,而非经过扩展语言来获得.
函数对象的多态包装器(又称多态函数对象包装器)在语义和语法上和函数指针类似,但不像函数指针那么狭隘。只要能被调用,且其参数能与包装器兼容的都能以称之为多态函数对象包装器(函数指针,成员函数指针或仿函数)。
经过如下例子,咱们能够了解多态函数对象包装器的特性:
std::function<int (int, int)> func; // 利用模板类 'function' // 创建包裝器 std::plus<int> add; // 'plus' 被声明 'template<class T> T plus( T, T ) ;' // 所以 'add' 的类型是 'int add( int x, int y )' func = &add; // 可行。'add' 的型参和回返值类型与 'func' 相符 int a = func (1, 2); // 注意: 若包裝器 'func' 沒有引用到任何函数 // 会抛出 'std::bad_function_call' 异常 std::function<bool (short, short)> func2 ; if(!func2) { // 由于还给'func2'赋值,此表达式为真 bool adjacent(long x, long y); func2 = &adjacent ; // 可行。'adjacent' 的型参和回返值类型可经过类型转换与'func2'相符 struct Test { bool operator()(short x, short y); }; Test car; func = std::ref(car); // 模板类 'std::ref' 返回一个struct 'car' 成员函数 'operator()' 的包裝 } func = func2; // 可行。'func2'的型参和回返值类型可经过类型转换而与'func' 相符
模板类 function 将定义在头文件 <functional>,而不须改动语言自己。
编写一个建立或修改其它程序(也能够是程序自己)的程序称为元编程.这种行为能够发生在编译期,也能够发生在运行期.C++标准委员会决定引入一个库,容许在编译期利用模板进行元编程.
如下是一个元编程的例子,基于当前的C++03标准: 模板的递归实例化来计算整数的幂
template<int B, int N> struct Pow { // 递归调用和从新组合. enum{ value = B*Pow<B, N-1>::value }; }; template< int B > struct Pow<B, 0> { // ''N == 0'' 是终止条件. enum{ value = 1 }; }; int quartic_of_three = Pow<3, 4>::value;
不少算法均可以操做不一样类型的数据;C++的模板支持泛型编程,而且使代码更加紧凑和有用.然而算法一般都须要知道它正使用的数据的类型.这些信息能够在编译期利用类型特征(type trait)获取到. 类型特征(type traits)能够标识出一个对象的类别和类或结构体的所有特征.type traits定义在一个新头文件<type_traits>中.
在下面的例子中,模板函数elaborate根据给定的数据类型实例化了一个特定的算法(algorithms.do_it)
// 算法一 template< bool B > struct Algorithm { template<class T1, class T2> static int do_it (T1 &, T2 &) { /*...*/ } }; // 算法二 template<> struct Algorithm<true> { template<class T1, class T2> static int do_it (T1, T2) { /*...*/ } }; // 根据给定的类型,会自动实例化正确的算法. template<class T1, class T2> int elaborate (T1 A, T2 B) { // 只有当T1是整数,T2是浮点数是实例化算法二 // 其余状况实例化算法一 return Algorithm<std::is_integral<T1>::value && std::is_floating_point<T2>::value>::do_it( A, B ) ; }
经过定义在<type_transform>头文件中的类型特征,能够建立类型转换操做(static_cast和const_cast对模板来讲是不够的).这种编程技术能产生优雅简洁的代码,可是除错倒是这种技术的弱点:编译期的错误信息难以理解,运行期错误则更难.
要在编译期肯定一个模板函数的返回值类型不是那么容易的,特别是当返回类型依赖于函数参数的时候.例如:
struct Clear { int operator()(int) const; // 参数类型 double operator()(double) const; // 与返回类型相同 }; template <class Obj> class Calculus { public: template<class Arg> Arg operator()(Arg& a) const { return member(a); } private: Obj member; }; //以Clear来实例化Calculus模板类(Calculus<Clear>), Calculus类的全部对象都有与Clear类相同的返回类型.可是下面给出的Confused类: struct Confused { double operator()(int) const; // 参数类型 int operator()(double) const; // 不一样于返回类型 }; //试图实例化Calculus<Confused>会致使Calculus与Confused的返回类型不一样.编译会产生一条从int转换到double的警告和一条从double转换到int的警告信息. //在TR1引入,C++11也接受了模板类std::result_of,它容许咱们在全部的声明中肯定和使用函数对象的返回类型.下面的类CalculusVer2用std::result_of对象来得到函数对象的返回类型: template< class Obj > class CalculusVer2 { public: template<class Arg> typename std::result_of<Obj(Arg)>::type operator()(Arg& a) const { return member(a); } private: Obj member; }; //这种实例化CalculusVer2<Confused>函数对象的方法没有类型转换,没有警告也没错误.
模板 std::result_of 在 TR1 和 C++11 的实现中有一点不一样。TR1 的版本容许实如今特殊状况下,可能没法决定一个函数调用其回返值的类别。然而,由于 C++11支持了decltype,实现被要求在全部状况下,皆能计算出回返值类型。
预计由 Technical Report 提供支持的:
延后讨论的:
结尾: 衷心但愿看到的朋友能给点意见或建议