C++11新标准解析

来源:互联网 发布:中国动漫知乎 编辑:程序博客网 时间:2024/05/06 21:18

                          C++11新标准解析

C++11,先前被称作C++0x,是目前计划中的C++编程语言的新标准。它将取代现行的C++标准ISO/IEC

14882,公开于1998年并于2003年更新,通称C++98以及C++03。新的标准将会包含核心语言的新机

能,而且会扩展C++标准程序库,并入了大部分的C++ Technical Report 1程序库(数学的特殊函数

除外)。最新的消息被公开在ISO C++ 委员会网站(英文)(http://www.open-std.org/jtc1/sc22

/wg21/docs/papers/) 

国际标准草案(N3126 (http://www.open-std.org/jtc1/sc22/wg21/docs

/papers/2010/n3126.pdf))20108月公开,201011月对该草案进行了修订(N3225

(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3225.pdf))

ISOIEC JTC1/SC22/WG21C++ 标准委员会目前的计划是在20108月之前完成对最终委员会草案的

投票,以及于20113月召开的标准会议完成国际标准的最终草案。然而,WG21预期ISO将要花费六个

月到一年的时间才能正式发布新的 C++ 标准。因此最快在2011年年底,我们才能见到新的C++标准。

为了能够如期完成,委员会决定致力于直至2006年为止的提案,忽略新的提案

[1]

C++这样的编程语言,通过一种演化的的过程来发展其定义。这个过程不可避免地将引发与现有代

码的兼容问题,在C++的发展过程中偶尔会发生。不过根据Bjarne StroustrupC++的创始人并且是

委员会的一员)表示,新的标准将几乎100%兼容于现有标准。

候选变更

2C++核心语言的扩充

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

3.1 右值引用和 move 语义

3.2 泛化的常数表示式

3.3 POD定义的修正

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

4.1 外部模板

核心语言使用性的加强

5.1 初始化列表

5.2 统一的初始化

5.3 类型推导

5.4 以范围为基础的 for 循环

5.5 Lambda函数与表示式

5.6 另一种的函数语法

5.7 对象建构的改良

5.8 显式虚函数重载

5.9 空指针

5.10 强类型枚举

5.11 角括号

5.12 显式类型转换子

5.13 模板的别名

5.14 无限制的unions

核心语言能力的提升

6.1 变长参数模板

6.2 新的字符串字面值

1页 共36页 2011/11/15 下午 13:51

6.3 用户自定义的字面值

6.4 多任务存储器模型

6.5 thread-local的存储期限

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

6.7 long long int类型

6.8 静态assertion

6.9 允许sizeof操作符作用在类型的数据成员上,无须明确的对象

6.10 垃圾回收机制

7C++标准程序库的变更

7.1 标准库组件上的升级

7.2 绪程支持

7.3 多元组类型

7.4 散列表

7.5 正则表达式

7.6 通用智能指针

7.7 可扩展的随机数功能

7.8 包装引用

7.9 多态函数对象包装器

7.10 用于元编程的类型属性

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

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

将被移除或废弃的特色

10 关项目

11 参考资料

11.1C++标准委员会文件

11.2 文章

12 外部链接

候选变更

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

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

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

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

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

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

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

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

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

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

使C++易于教授与学习

对初学者的注重被认为是重要的,因为他们构成了计算机程序员的主体。也因为许多初学者不愿扩展

他们对 C++ 的知识,只限于使用他们对 C++ 专精的部分。此外,考虑到 C++ 被广泛的使用(包含

应用领域和编程风格),即便是最有经验的程序员在面对新的编程范式时也会为初学者。

C++核心语言的扩充

C++委员会的主要焦点是在语言核心的发展上。核心语言将被大幅改善的领域包括多线程支持、泛型

编程、统一的初始化,以及性能表现的加强。

2页 共36页 2011/11/15 下午 13:51

在此分成4个区块来讨论核心语言的特色以及变更: 运行期表现强化、建构期表现强化、可用性强

化,还有新的功能。某些特色可能会同时属于多个区块,但在此仅于其最具代表性的区块描述该特

色。

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

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

右值引用和 move 语义

在 C++03及之前的标准,临时对象(称为右值"R-values",位于赋值操作符之右)无法被改变,在C

中亦同(且被视为无法和 const T& 做出区分)。尽管在某些情况下临时对象的确会被改变,甚至也

被视为是一个有用的漏洞。

C++11 增加一个新的非常数引用(reference)类型,称作右值引用(R-value reference),标记为

T &&。右值引用所引用的临时对象可以在该临时对象被初始化之后做修改,这是为了允许move 

义。

C++03 性能上被长期被诟病的其中之一,就是其耗时且不必要的深度拷贝。深度拷贝会发生在当对象

是以传值的方式传递。举例而言,std::vector<T> 是内部保存了 C-style 数组的一个包装,如果一

std::vector<T>的临时对象被建构或是从函数返回,要将其存储只能通过生成新的std::vector<T>

并且把该临时对象所有的数据复制进去。该临时对象和其拥有的内存会被摧毁。(为了讨论上的方

便,这里忽略返回值优化)

在 C++11,一个std::vector的 "move 建构子对某个vector的右值引用可以单纯地从右值复制其内

部 C-style 数组的指针到新的vector,然后留下空的右值。这个操作不需要数组的复制,而且空的

暂时对象的解构也不会摧毁存储器。传回vector暂时对象的函数只需要传回std::vector<T>&&。如果

vector没有move 建构子,那么复制建构子将被调用,以const std::vector<T> &的正常形式。 如

果它确实有 move 建构子,那么就会调用move 建构子,这能够免除大幅的存储器配置。

基于安全的理由,具名的变量将永远不被认定为右值,即使它是被如此声明的;为了获得右值必须使

用 std::move<T>()

boolis_r_value(int &&) { return true; }

boolis_r_value(const int &) { return false; }

voidtest(int &&i)

{

is_r_value(i); // i 為具名變數,即使被宣告成右值也不會被認定是右值。

is_r_value(std::move<int>(i));// 使用std::move<T>() 取得右值。

}

由于右值引用的用语特性以及对于左值引用(L-valuereferences;regular references)的某些用

语修正,右值引用允许开发者提供完美转发 (perfectfunction forwarding)。当与变长参数模板结

合,这项能力允许函数模板能够完美地转送引数给其他接受这些特定引数的函数。最大的用处在于转

送建构子参数,创造出能够自动为这些特定引数调用正确建构式的工厂函数(factory function)。

泛化的常数表示式

C++ 本来就已具备常数表示式(constant expression)的概念。像是3+4 总是会产生相同的结果并

且没有任何的副作用。常数表示式对编译器来说是优化的机会,编译器时常在编译期运行它们并且将

值存入程序中。同样地,在许多场合下,C++ 规格要求使用常数表示式。例如在数组大小的定义上,

以及枚举值(enumerator values)都要求必须是常数表示式。

3页 共36页 2011/11/15 下午 13:51

然而,常数表示式总是在遇上了函数调用或是对象建构式时就终结。所以像是以下的例子是不合法

的:

int GetFive() {return 5;}

int some_value[GetFive() + 5]// 欲產生 10 個整數的陣列。 不合法的C++ 寫法

这不是合法的C++,因为 GetFive() +5 并不是常数表示式。编译器无从得知 GetFive 实际上在运

行期是常数。理论上而言,这个函数可能会影响全局变量,或者调用其他的非运行期(non-runtime)常数函数等。

C++11引进关键字constexpr 允许用户保证函数或是对象建构式是编译期常数。以上的例子可以被写

像是下面这样:

constexpr int GetFive() {return 5;}

int some_value[GetFive() + 5]// 欲產生 10 個整數的陣列。合法的C++11寫法

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

对函数使用 constexpr 在函数可以做的事上面加上了非常严格的条件。首先,该函数的回返值类型

不能为 void。第二点,函数的内容必须依照"return expr" 的形式。第三点,在引数取代后,expr

必须是个常数表示式。这些常数表示式只能够调用其他被定义为 constexpr 的函数,或是其他常数

表示式的数据变量。 最后一点,有着这样标签的函数直到在该编译单元内被定义之前是不能够被调

用的。

变量也可以被定义为常数表示式值:

constexpr doubleforceOfGravity = 9.8;

constexpr doublemoonGravity =forceOfGravity / 6.0;

常数表示式的数据变量是隐式的常数。他们可以只存储常数表示式或常数表示式建构式的结果。

为了从用户自定类型(user-defined type)建构常数表示式的数据变量,建构式也可以被声明成

constexpr。与常数表示式函数一样,常数表示式的建构式必须在该编译单元内使用之前被定义。他

必须有着空的函数本体。它必须用常数表示式初始化他的成员(member)。而这种类型的解构式应当

是无意义的(trivial),什么事都不做。

复制constexpr 建构起来的类型也应该被定义为constexpr,这样可以让他们从常数表示式的函数

以值传回。类型的任何成员函数,像是复制建构式、重载的操作符等等,只要他们符合常数表示式函

数的定义,都可以被声明成constexpr。这使得编译器能够在编译期进行类型的复制、对他们施行运

算等等。

常数表示式函数或建构式,可以以非常数表示式(non-constexpr)参数唤起。就如同constexpr 

数字面值能够指派给 non-constexpr 变量,constexpr 函数也可以接受 non-constexpr 参数,其结

果存储于non-constexpr 变量。constexpr 关键字只有当表示式的成员都是 constexpr,才允许编

译期常数性的可能。

POD定义的修正

在标准C++,一个结构(struct)为了能够被当成plain old data(POD),必须遵守几条规则。有很

好的理由使我们想让大量的类型符合这种定义,符合这种定义的类型能够允许产生与C兼容的对象布

4页 共36页 2011/11/15 下午 13:51

局(objectlayout)。然而,C++03的规则太苛了。

C++11将会放宽关于POD的定义。

class/struct是极简的(trivial)、属于标准布局(standard-layout),以及他的所有非静态(non-static)成员都是POD时,会被视为POD

一个极简的类型或结构符合以下定义:

极简的默认建构式。这可以使用默认建构式语法,例如SomeConstructor()= default; 1.

极简的复制建构式,可使用默认语法(default syntax) 2.

极简的赋值操作符,可使用默认语法(default syntax) 3.

极简的解构式,不可以是虚拟的(virtual) 4.

一个标准布局(standard-layout)的类型或结构符合以下定义:

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

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

没有虚函数 3.

没有虚拟基类 4.

只有符合标准布局的基类 5.

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

若非没有带有non-static成员的基类,就是最底层(继承最末位)的类型没有non-static数据

成员而且至多一个带有non-static成员的基类。基本上,在该类型的继承体系中只会有一个类

型带有non-static成员。

7.

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

外部模板

在标准C++中,只要在编译单元内遇到被完整定义的模板,编译器都必须将其实例化

instantiate)。这会大大增加编译时间,特别是模板在许多编译单元内使用相同的参数实例化。

看起来没有办法告诉C++不要引发模板的实例化。

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

template class std::vector<MyClass>;

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

下:

extern template class std::vector<MyClass>;

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

核心语言使用性的加强

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

易误用代码之类的。

初始化列表

5页 共36页 2011/11/15 下午 13:51

标准C++C带来了初始化列表(initializer list)的概念。这个构想是结构或是数组能够依据成员

在该结构内定义的顺序通过给予的一串引数来产生。这些初始化列表是递归的,所以结构的数组或是

包含其他结构的结构可以使用它们。这对静态列表或是仅是把结构初始化为某值而言相当有用。C++

有构造函数,能够重复对象的初始化。但单单只有那样并不足以取代这项特色的所有机能。除了这些

对象必须遵守POD的定义的限制条件,标准C++允许在结构或类型上使用这项机能;非POD的类型不能

使用,就连相当有用的C++-style容器像是std::vector也不行。

C++11将会把初始化列表的概念绑到类型上,称作std::initializer_list。这允许构造函数或其他函

数像参数般地使用初始化列表。举例来说:

classSequenceClass

{

public:

SequenceClass(std::initializer_list<int> list);

};

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

SequenceClass someVar = {1, 4, 5, 6};

这个构造函数是种特殊的构造函数,称作初始化列表构造函数。有着这种构造函数的类型在统一初始

化的时候会被特别对待。

类型std::initializer_list<>是个第一级的C++11标准程序库类型。然而他们只能够经由C++11编译

器通过{}语法的使用被静态地构造 。这个列表一经构造便可复制,虽然这只是copy-by-reference

初始化列表是常数;一旦被创建,其成员均不能被改变,成员中的数据也不能够被变动。

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

化列表作为引数。例如:

voidFunctionName(std::initializer_list<float>list);

FunctionName({1.0f, -3.45f, -0.4f});

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

vector<string> v = { "xyzzy", "plugh", "abracadabra" };

统一的初始化

标准C++ 在初始化类型方面有着许多问题。初始化类型有数种方法,而且交换使用时不会都产生相

同结果。传统的建构式语法,看起来像是函数声明,而且为了能使编译器不会弄错必须采取一些步

骤。只有集合体和POD 类型能够被集合式的初始化(使用 SomeType var = {/*stuff*/};.

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

struct BasicStruct

{

intx;

6页 共36页 2011/11/15 下午 13:51

floaty;

};

struct AltStruct

{

AltStruct(int_x, float _y) :x(_x),y(_y) {}

private:

intx;

floaty;

};

BasicStructvar1{5, 3.2f};

AltStruct var2{2, 4.3f};

var1 的初始化的运作就如同 C-style 的初始化串行。每个公开的变量将被对应于初始化串行的值给

初始化。隐式类型转换会在需要的时候被使用,这里的隐式类型转换不会产生范围缩限

(narrowing)。要是不能够转换,编译便会失败。(范围缩限 (narrowing):转换后的类型无法表示原

类型。如将 32-bit 的整数转换为 16-bit 或 8-bit 整数,或是浮点数转换为整数。) var2 的初始

化则是简单地调用建构式。

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

struct IdString

{

std::stringname;

intidentifier;

};

IdStringvar3{"SomeName", 4};

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

IdStringGetString()

{

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

}

统一初始化不会取代建构式语法。仍然会有需要用到建构式语法的时候。如果一个类型拥有初始化串

行建构式(TypeName(initializer_list<SomeType>);),而初始化串行符合sequence 建构式的类

型,那么它比其他形式的建构式的优先权都来的高。C++11 版本的 std::vector 将会有初始化串行

建构式。这表示:

std::vector<int>theVec{4};

这将会调用初始化串行建构式,而不是调用std::vector只接受一个尺寸参数产生相应尺寸vector

的建构式。要使用这个建构式,用户必须直接使用标准的建构式语法。

类型推导

7页 共36页 2011/11/15 下午 13:51

在标准 C++(和 ),使用变量必须明确的指出其类型。然而,随着模版类型的出现以及模板超编

程的技巧,某物的类型,特别是函数定义明确的回返类型,就不容易表示。在这样的情况下,将中间

结果存储于变量是件困难的事,可能会需要知道特定的超编程程序库的内部情况。

C++11 提供两种方法缓解上述所遇到的困难。首先,有被明确初始化的变量可以使用auto 关键字。

这会依据该初始化子(initializer)的具体类型产生变量:

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

autootherVariable = 5;

someStrangeCallableType 的类型就是模板函数 boost::bind 对特定引数所回返的类型。作为编译

器语义分析责任的一部份,这个类型能够简单地被编译器决定,但用户要通过查看来判断类型就不是

那么容易的一件事了。

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

数字面值的类型一样。

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

int someInt;

decltype(someInt) otherIntegerVariable = 5;

decltype auto 一起使用会更为有用,因为 auto 变量的类型只有编译器知道。然而 decltype

对于那些大量运用操作符重载和特化的类型的代码的表示也非常有用。

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

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

而可以用更简短的

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

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

法。

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

#include<vector>

int main()

{

const std::vector<int>v(1);

autoa =v[0]// a 為 int 型別

decltypev[0]b;  // b 為 constint& 型別,即

// std::vector<int>::operator[]size_typeconst 的回返型別

autoc = 0;     // c 為 int 型別

autod =c;     // d 為 int 型別 

decltype(c)e;   // e int 型別,實體的型別

decltype((c))f =e; // f 為 int& 型別,因為(c)是左值

8页 共36页 2011/11/15 下午 13:51

decltype(0)g;   //gint型別,因為0是右值

}

学习

以范围为基础的 for 循环

Boost C++ 定义了许多"范围 (range)"的概念。范围表现有如受控制的串行(list),持有容器中的

两点。有序容器是范围概念的超集 (superset),有序容器中的两个迭代器 (iterator) 也能定义一

个范围。这些概念以及操作的算法,将被并入C++11 标准程序库。不过 C++11 将会以语言层次的支

持来提供范围概念的效用。

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

int my_array[5] = {1, 2, 3, 4, 5};

for(int &x :my_array)

{

x *= 2;

}

上面for 述句的第一部份定义被用来做范围迭代的变量,就像被声明在一般 for 循环的变量一样,

其作用域仅只于循环的范围。而在":"之后的第二区块,代表将被迭代的范围。这样一来,就有了能

够允许 C-style 数组被转换成范围概念的概念图。这可以是std::vector,或是其他符合范围概念

的对象。

Lambda函数与表示式

在标准 C++,特别是当使用C++ 标准程序库算法函数诸如 sort 和 find,用户经常希望能够在算法

函数调用的附近定义一个临时的述部函数(又称谓词函数,predicate function)。由于语言本身允许

在函数内部定义类型,可以考虑使用函数对象,然而这通常既麻烦又冗赘,也阻碍了代码的流程。此

外,标准C++ 不允许定义于函数内部的类型被用于模板,所以前述的作法是不可行的。

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

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

[](int x, inty) { return x +y; }

这个不具名函数的回返类型是 decltype(x+y)。只有在 lambda 函数符合"return expression"的形

式下,它的回返类型才能被忽略。在前述的情况下,lambda 函数仅能为一个述句。

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

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

本例中,一个暂时的变量 被创建用来存储中间结果。如同一般的函数,的值不会保留到下一次

该不具名函数再次被调用时。

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

同作用域的变量参考也可以被使用。这种的变量集合一般被称作 closure(闭包)

9页 共36页 2011/11/15 下午 13:51

[]  // 沒有定義任何變數。使用未定義變數會導致錯誤。

[x, &y] // x 以傳值方式傳入(預設)以傳參考方式傳入。

[&]  // 任何被使用到的外部變數皆隱式地以參考方式加以引用。

[=]  // 任何被使用到的外部變數皆隱式地以傳值方式加以引用。

[&, x]   // x 顯示地以傳值方式加以引用。其餘變數以參考方式加以引用。

[=, &z]  // z 顯示地以參考方式加以引用。其餘變數以傳值方式加以引用。

closure 被定义与使用如下:

std::vector<int>someList;

int total = 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::vector<int>someList;

int total = 0;

std::for_each(someList.begin(), someList.end(), [&](intx) {

total +=x;

});

变量传入lambda 函数的方式可能随实做有所变化,一般期望的方法是 lambda 函数能保留其作用域

函数的 stack 指针,借此访问区域变量。

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

对于不同的变量,传值或传参考可以混和使用。 比方说,用户可以让所有的变量都以传参考的方式

使用,但带有一个传值使用的变量:

int total = 0;

int value = 5;

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

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

若一个 lambda 函数被定义于某类型的成员函数中,会被当作该类型的 friend。像这样的 lambda

函数可以使用该类型对象的参考,并且能够访问其内部的成员。

10页 共36页 2011/11/15 下午 13:51

[](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 = new auto([=] { /*...*/ });

但是,如果 lambda 函数是以参考的方式获取到它所有的 closure 变量,或者是没有 closure 

量,那么所产生的函数对象会被给予一个特殊的类型: std::reference_closure<R(P)>,其中 R(P)

是包含回返类型的函数签名。比起由std::function 获取而来,这会是lambda函数更有效率的代

表:

std::reference_closure<void()> myLambdaFunc = [this]() { this->SomePrivateMemberFunction

myLambdaFunc();

另一种的函数语法

标准函数声明语法对于C语言已经足够。 演化自C++ 除了的基础语法外,又扩充额外的

语法。 然而,当C++ 变得更为复杂时,它暴露出许多语法上的限制, 特别是针对函数模板的声

明。 下面的示例,不是合法的 C++03

template< typename LHS, typename RHS>

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

Ret 的类型由LHSRHS相加之后的结果的类型来决定。 即使使用 C++11 新加入的decltype 来声

明 AddingFunc 的返回类型,依然不可行。

template< typename LHS, typename RHS>

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

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

rhs 才是有意义的。

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

11页 共36页 2011/11/15 下午 13:51

template< typename LHS, typename RHS>

autoAddingFunc(constLHS &lhs, const RHS &rhs) ->decltype(lhs+rhs) {returnlhs +rhs

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

struct SomeStruct

{

autoFuncName(intx, inty) -> int;

};

[]SomeStruct::FuncName(int x, inty) -> int

{

return x +y;

}

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

对象建构的改良

在标准C++中,建构式不能调用其它的建构式;每个建构式必须自己初始化所有的成员或是调用一个

共用的成员函数。基类的建构式不能够直接作为派生类的建构式;就算基类的建构式已经足够,每个

衍伸的类型仍必须实做自己的建构式。类型中non-constant的数据成员不能够在声明的地方被初始

化,它们只能在建构式中被初始化。C++11将会提供这些问题的解决方案。

C++11允许建构式调用其他建构式,这种做法称作委托或转接(delegation)。 仅仅只需要加入少量

的代码,就能让数个建构式之间达成功能复用(reuse)。 Java以及C#都有提供这种功能。C++11 语法

如下:

classSomeType {

intnumber;

string name;

SomeType( int i, string& s ) :number(i), name(s){}

public:

SomeType( )        : SomeType( 0, "invalid" ){}

SomeType( int i )   :SomeType( i, "guest" ){}

SomeType( string&s ) :SomeType( 1,s ){PostInit(); }

};

C++03中,建构式运行退出代表对象建构完成; 而允许使用转接建构式的C++11 则是以"任何"一个

建构式退出代表建构完成。 使用转接的建构式,函数本体中的代码将于被转接的建构式完成后继续

运行(如上例的 PostInit())。 若基底类型使用了转接建构式,则派生类的建构式会在"所有"基底类

型的建构式都完成后, 才会开始运行。

C++11 允许派生类手动继承基底类型的建构式, 编译器可以使用基底类型的建构式完成派生类的建

构。 而将基类的建构式带入派生类的动作, 无法选择性地部分带入, 要不就是继承基类全部的建

构式,要不就是一个都不继承(不手动带入)。 此外,若牵涉到多重继承,从多个基底类型继承而来

的建构式不可以有相同的函数签名(signature)。 而派生类的新加入的建构式也不可以和继承而来的

基底建构式有相同的函数签名,因为这相当于重复声明。

语法如下:

12页 共36页 2011/11/15 下午 13:51

classBaseClass

{

public:

BaseClass(intiValue);

};

classDerivedClass : publicBaseClass

{

public:

using BaseClass::BaseClass;

};

此语法等同于DerivedClass 声明一个DerivedClass(int) 的建构式。 同时也因为DerivedClass

有了一个继承而来的建构式,所以不会有默认建构式。

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

classSomeClass

{

public:

SomeClass() {}

explicit SomeClass(int iNewValue) :iValue(iNewValue) {}

private:

intiValue = 5;

};

若是建构式中没有设置iValue的初始值,则会采用类定义中的成员初始化,令iValue初值为5。在上

例中,无参数版本的建构式,iValue便采用默认所定义的值; 而带有一个整数参数的建构式则会以

指定的值完成初始化。

成员初始化除了上例中的赋值形式(使用"=")外,也可以采用建构式以及统一形的初始化(uniform

initialization,使用"{}")

显式虚函数重载

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

struct Base {

virtual void some_func();

};

struct Derived :Base {

void some_func();

};

Derived::some_func 的真实意图为何程序员真的试图重载该虚函数,或这只是意外这也可能是

base 的维护者在其中加入了一个与Derived::some_func 同名且拥有相同签名的虚函数。

另一个可能的状况是,当基类中的虚函数的签名被改变,子类中拥有旧签名的函数就不再重载该虚函

数。因此,如果程序员忘记修改所有子类,运行期将不会正确调用到该虚函数正确的实现。

13页 共36页 2011/11/15 下午 13:51

C++11 将加入支持用来防止上述情形产生,并在编译期而非运行期捕获此类错误。为保持向后兼容,

此功能将是选择性的。其语法如下:

classB {

typedef Bself;

virtual void some_func1();

virtual void some_func2(float);

virtual void some_func3() const;

virtual long some_func4(int);

virtual voidf();

virtual voidh(int);

voidj(int);

voidk(int);

};

classD [[base_check]] : publicB {

usingB::j;

void sone_func1 [[override]] ();  // 錯誤格式錯誤的函式名稱

void some_func2 [[override]] (double);// 錯誤格式錯誤的參數型別

void some_func3 [[override]] (); // 錯誤格式沒有加上 cv-qualification

intsome_func4 [[override]] (int);// 錯誤格式不符合B::some_func4 的回返型別

virtual void f [[override]] ();// 正確重載 B::f

virtual voidg(long);   // 引入新的虛函式

voidh(int);         // 錯誤格式重載為加上[[override]]

virtual voidh(double);  // 錯誤格式新的虛函式隱藏了 voidh(int)

virtual void h [[hiding]] (char *);// 正確新的虛函式隱藏了 voidh(int)

virtual   intj( double );      // 正確使用 using B::j 防止隱藏

intk( double );       // 錯誤格式:name hiding and nousing declaration

doublek [[hiding]] ( char * );// 正確,hidingis clearlyindicated

doublem [[hiding]] ( char * ); // 錯誤格式,hidingis requested,butnotpresent

typedef Dself; // ill-formed, newtype definition hidesthedefinition inB

};

一个class/struct 若带有 [[base_check]] 属性,则意谓著任何隐式重载将会导致编译期错误。所

有的重载都必须加上 [[override]] 属性。[[hiding]] 意谓著新函数隐藏了基类的函数。

空指针

早在1972 年,C语言诞生的初期,常数带有常数及空指针的双重身分。使用preprocessor

macro NULL 表示空指针, 让NULL 分别代表空指针及常数0。 NULL 可被定义为((void*)0)

或是0

C++ 并不采用的规则,不允许将void* 隐式转换为其他类型的指针。 为了使代码 char* c=

NULL; 能通过编译,NULL 只能定义为0。 这样的决定使得函数重载无法区分代码的语义:

voidfoo(char *);

voidfoo(int);

C++ 建议 NULL 应当定义为0,所以foo(NULL); 将会调用foo(int), 这并不是程序员想要的行

为,也违反了代码的直观性。的歧义在此处造成困扰。

C++11 引入了新的关键字来代表空指针常数:nullptr,将空指针和整数的概念拆开。 nullptr

的类型为nullptr_t,能隐式转换为任何指针或是成员指针的类型,也能和它们进行相等或不等的比

14页 共36页 2011/11/15 下午 13:51

较。 而nullptr不能隐式转换为整数,也不能和整数做比较。

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

char*pc = nullptr;   // OK

int *pi =nullptr;   // OK

int  i =nullptr;  //error

foo(nullptr);     // 呼叫foo(char *)

强类型枚举

在标准C++中,枚举类型不是类型安全的。枚举类型被视为整数,这使得两种不同的枚举类型之间可

以进行比较。C++03 唯一提供的安全机制是一个整数或一个枚举型值不能隐式转换到另一个枚举别

型。 此外,枚举所使用整数类型及其大小都由实现方法定义,皆无法明确指定。 最后,枚举的名称

全数暴露于一般范围中,因此两个不同的枚举,不可以有相同的枚举名。(好比enum Side{ Right,

Left }; enum Thing{ Wrong, Right}; 不能一起使用。)

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

enum class Enumeration

{

Val1,

Val2,

Val3 = 100,

Val4 /* = 101 */,

};

此种枚举为类型安全的。枚举类型不能隐式地转换为整数;也无法与整数数值做比较。(表示式

Enumeration::Val4 ==101 会触发编译期错误)

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

型:

enum class Enum2 : unsigned int {Val1, Val2};

枚举类型的语汇范围(scoping)定义于枚举类型的名称范围中。 使用枚举类型的枚举名时,必须明确

指定其所属范围。 由前述枚举类型 Enum2 为例,Enum2::Val1是有意义的表示法, 而单独的 Val1

则否。

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

enumEnum3 : unsigned long {Val1 = 1, Val2};

枚举名 Val1 定义于 Enum3 的枚举范围中(Enum3::Val1),但为了兼容性,Val1 仍然可以于一般的

范围中单独使用。

在 C++11 中,枚举类型的前置声明(forward declaration) 也是可行的,只要使用可指定类型的新

式枚举即可。 之前的C++ 无法写出枚举的前置声明,是由于无法确定枚举变量所占的空间大小,

C++11 解决了这个问题:

15页 共36页 2011/11/15 下午 13:51

enumEnum1;            // 不合法的 C++ C++11; 無法判別大小

enumEnum2 : unsigned int;   // 合法的C++11

enum class Enum3;         // 合法的 C++11,列舉類別使用預設型別 int 

enum class Enum4: unsigned int;// 合法的 C++11

enumEnum2 : unsigned short;   // 不合法的C++11Enum2 已被聲明為unsigned int

角括号

标准C++ 的剖析器一律将">>" 视为右移操作符。 但在样板定义式中,绝大多数的场合其实都代表

两个连续右角括号。 为了避免剖析器误判,撰码时不能把右角括号连着写。

C++11 变更了剖析器的解读规则;当遇到连续的右角括号时,优先解析右角括号为样板引数的退出符

号。 如果解读过程中出现普通括号("(" 与 ")"),这条规则产生变化:

template<bool bTest> SomeType;

std::vector<SomeType<1>2>>x1;   // 解讀為std::vector of "SomeType<true>2>"

// 非法的表示式, 整數被轉換為 bool 型別true

std::vector<SomeType<(1>2)>>x1;// 解讀為std::vector of "SomeType<false>",

// 合法的C++11 表示式,(1>2) 被轉換為 bool 型別false

显式类型转换子

C++ 为了避免用户自定的单引数建构式被当成隐式类型转换子,引入了关键字 explicit 修饰字。

但是,在编译器对对象调用隐式类型转换的部分,则没有任何着墨。 比方说,一个 smart pointer

类型具有一个operator bool(), 被定义成若该smart pointer 保管任何资源或指针,则传回

true,反之传回false。 遇到这样的代码时:if(smart_ptr_variable),编译器可以借由 operator

bool() 隐式转换布林值, 和测试原生指针的方法一样。 但是这类隐式转换同样也会发生在非预

期之处。由于C++ bool 类型也是算数类型,能隐式换为整数甚至是浮点数。 拿对象转换出的布

林值做布林运算以外的数学运算,往往不是程序员想要的。

在 C++11 中,关键字explicit 修饰符也能套用到类型转换子上。如同建构式一样,它能避免类型

转换子被隐式转换调用。但C++11 特别针对布林值转换提出规范,在if 条件式,循环,逻辑运算

等需要布林值的地方,编译器能为符合规范的表示式调用用户自定的布林类型转换子。

模板的别名

在进入这个主题之前,各位应该先弄清楚模板类型本质上的不同。class template(类型

模板,是模板)是用来产生template class(模板类型,是类型)

在标准 C++typedef 可定义模板类型一个新的类型名称,但是不能够使用 typedef 来定义模板的

别名。举例来说:

template< typename first, typenamesecond, int third>

classSomeType;

template< typename second>

typedef SomeType<OtherType, second, 5> TypedefName;// C++是不合法的

这不能够通过编译。

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

16页 共36页 2011/11/15 下午 13:51

template< typename first, typenamesecond, int third>

classSomeType;

template< typename second>

usingTypedefName =SomeType<OtherType, second, 5>;

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

typedef void (*PFD)(double);        // 傳統語法

usingPFD = void (*)(double);    // 新增語法

无限制的unions

在标准 C++ 中,并非任意的类型都能做为union 的成员。比方说,带有non-trivial 构造函数的

类型就不能是union 的成员。在新的标准里,移除了所有对union 的使用限制,除了其成员仍然不

能是引用类型。 这一改变使得 union 更强大,更有用,也易于使用。

[1]

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

struct point

{

point() {}

point(int x, inty): x_(x), y_(y) {}

intx_,y_;

};

union

{

intz;

doublew;

point p;  // 不合法的C++; point 有一 non-trivial 建構式

// 合法的C++11

};

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

核心语言能力的提升

这些机能提供了C++语言能够做一些事情是以前所不能达成的,或是在以前需要繁琐的写法、要求一

些不可移植的程序库。

变长参数模板

在 C++11 之前不论是模板类或是模板函数,都只能按其被声明时所指定的样子,接受一组固定数

目的模板实参; C++11 加入新的表示法,允许任意个数、任意类别的模板实参,不必在定义时将实

参的个数固定。

template<typename... Values> class tuple;

17页 共36页 2011/11/15 下午 13:51

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

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

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

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

template<typename First, typename... Rest> classtuple;

变长参数模板也能运用到模板函数上。 传统 中的printf 函数,虽然也能达不定个数的形参的

调用,但其并非类别安全。 以下的样例中,C++11 除了能定义类别安全的变长参数函数外,还能让

类似printf 的函数能自然地处理非自带类别的对象。 除了在模板实参中能使用...表示不定长模板

实参外,函数实参也使用同样的表示法代表不定长实参。

template<typename... Params> void printf(const std::string &strFormat, Params... parameters

其中,Params parameters 分别代表模板与函数的变长参数集合, 称之为实参包 (parameter

pack)。实参包必须要和运算符"..."搭配使用,避免语法上的歧义。

变长参数模板中,变长参数包无法如同一般实参在类或函数中使用; 因此典型的手法是以递归的方

法取出可用实参,参看以下的 C++11 printf 样例:

void printf(const char *s)

{

while (*s)

{

if (*s == '%' && *(++s) != '%')

throwstd::runtime_error("invalid format string: missing arguments");

std::cout << *s++;

}

}

template<typename T, typename... Args>

void printf(const char*s,T value, Args... args)

{

while (*s)

{

if (*s == '%' && *(++s) != '%')

{

std::cout <<value;

printf(*s ? ++s : s, args...); // 即便当 *s ==0 也会产生调用,以检测更多的类型参数。

return;

}

std::cout << *s++;

}

throw std::logic_error("extra arguments provided to printf");

}

printf 会不断地递归调用自身:函数实参包args... 在调用时, 会被模板类别匹配分离为T

18页 共36页 2011/11/15 下午 13:51

valueArgs... args。 直到 args... 变为空实参,则会与简单的 printf(const char *s) 形成

匹配,退出递归。

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

template<>

struct count<> {

static const intvalue = 0;

};

template<typename T, typename... Args>

struct count<T,Args...> {

static const intvalue = 1 +count<Args...>::value;

};

虽然没有一个简洁的机制能够对变长参数模板中的值进行迭代,但使用运算符"..."还能在代码各处

对实参包施以更复杂的展开操作。举例来说,一个模板类的定义:

template <typename... BaseClasses> classClassName : publicBaseClasses...

{

public:

ClassName (BaseClasses&&... baseClasses) :BaseClasses(baseClasses)... {}

}

BaseClasses... 会被展开成类型ClassName 的基底类; ClassName 的构造函数需要所有基类的左

值引用,而每一个基类都是以传入的实参做初始化(BaseClasses(baseClasses)...)

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

template<typename TypeToConstruct> struct SharedPtrAllocator

{

template<typename... Args> std::shared_ptr<TypeToConstruct>ConstructWithSharedPtr(

{

returntr1::shared_ptr<TypeToConstruct>(newTypeToConstruct(std::forward<Args>(params

}

}

实参包 parms 可展开为 TypeToConstruct 构造函数的形参。 表达式std::forward<Args>(params)

可将形参的类别信息保留(利用右值引用),传入构造函数。 而运算符"..."则能将前述的表达式套用

到每一个实参包中的实参。这种工厂函数(factory function)的手法, 使用 std::shared_ptr 

理配置对象的存储器,避免了不当使用所产生的存储器泄漏(memoryleaks)。

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

template<typename ...Args> structSomeStruct

{

static const int size = sizeof...(Args);

}

19页 共36页 2011/11/15 下午 13:51

SomeStruct<Type1, Type2>::size 2,而SomeStruct<>::size 会是0。 (sizeof...(Args) 

结果是编译期常数。)

新的字符串字面值

标准C++提供了两种字符串字面值。第一种,包含有双引号,产生以空字符结尾的constchar数组。

第二种有着前标L,产生以空字符结尾的const wchar_t数组,其中wchar_t代表宽字符。对于Unicode

编码的支持尚付阙如。

为了加强C++编译器对Unicode的支持,类别char的定义被修改为其大小至少能够存储UTF-88位编

码,并且能够容纳编译器的基本字符集的任何成员。

C++11 将支持三种Unicode编码方式:UTF-8UTF-16,和UTF-32。除了上述char定义的变更, C++11

将增加两种新的字符类别:char16_tchar32_t。它们各自被设计用来存储UTF-16 以及UTF-32的字

符。

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

u8"I'm aUTF-8 string."

u"This is a UTF-16 string."

U"This is a UTF-32 string."

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

串的类别是const char32_t[]

当创建Unicode字符串字面值时,可以直接在字符串内插入UnicodecodepointsC++11提供了以下的

语法:

u8"This is aUnicode Character: \u2018."

u"This is a bigger UnicodeCharacter: \u2018."

U"This is a UnicodeCharacter: \u2018."

'\u'之后的是16个比特的十六进制数值;它不需要'0x'的前标。识别字'\u'代表了一个16位的

Unicode codepoint;如果要输入32位的codepoint,使用'\U'32个比特的十六进制数值。只有有效

Unicodecodepoints能够被输入。举例而言,codepoints在范围U+D800—U+DFFF之间是被禁止的,

它们被保留给UTF-16编码的surrogatepairs

有时候避免手动将字符串换码也是很有用的,特别是在使用XML文件或是一些脚本语言的字面值的时

候。C++11将提供raw(未加工的)字符串字面值:

R"(The String Data \Stuff" )"

R"delimiter(The String Data \ Stuff " )delimiter"

在第一个例子中,任何包含在()括号(标准已经从[]改为())当中的都是字符串的一部分。其中"\

字符不需要经过跳脱(escaped)。在第二个例子中,"delimiter(开始字符串,只有在遇

)delimiter"才代表退出。其中delimiter可以是任意的字符串,能够允许用户在未加工的字符串字

面值中使用)字符。 未加工的字符串字面值能够和宽字面值或是Unicode字面值结合:

u8R"XXX(I'ma "raw UTF-8" string.)XXX"

uR"*@(This is a"raw UTF-16" string.)*@"

UR"(Thisis a "raw UTF-32"string.)"

20页 共36页 2011/11/15 下午 13:51

用户自定义的面值

标准C++提供了数种字面值。字符"12.5"是能够被编译器解释为数值12.5double类别字面值。然

而,加上"f"的后置,像是"12.5f",则会产生数值为12.5float类别字面值。在C++规范中字面值的

后置是固定的,而且C++代码并不允许创立新的字面后置。

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

象。

字面值转换可以区分为两个阶段:转换前与转换后(raw cooked)。 转换前的字面值指特定字符

串行,而转换后的字面值则代表另一种类别。 如字面值1234,转换前的字面值代表'1', '2', '3',

'4' 的字符串行; 而转换后,字面值代表整数值1234。 另外,字面值0xA转换前是串行'0', 'x',

'A';转换后代表整数值 10

多任务存储器模型

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

这将涉及两个部分:第一、设计一个可以使多个线程在一个进程中共存的内存模型;第二、为线程之

间的交互提供支持。第二部分将由程序库提供支持,更多请看绪程支持。

在多个线程可能会访问相同内存的情形下,由一个内存模型对它们进行调度是非常有必要的。遵守模

型规则的程序是被保证正确运行的,但违反规则的程序会发生不可预料的行为,这些行为依赖于编译

器的优化和存储器一致性的问题。

thread-local的存储期限

在多线程环境下,让各绪程拥有各自的变量是很普遍的。这已经存在于函数的区域变量,但是对于全

局和静态变量都还不行。

新的thread_local存储期限(在现行的staticdynamicautomatic之外)被作为下个标准而提出。

绪程区域的存储期限会借由存储指定字thread_local来表明。

static对象(生命周期为整个程序的运行期间)的存储期限可以被thread-local给替代。就如同其他

使用static存储期的变量,thread-local对象能够以构造函数初始化并以解构式摧毁。

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

在传统C++中,若用户没有提供, 则编译器会自动为对象生成默认构造函数(default

constructor)、 复制构造函数(copyconstructor),赋值操作符(copyassignmentoperator

operator=) 以及解构式(destructor)。另外,C++也为所有的类定义了数个全局运算符(operator

deleteoperatornew)。当用户有需要时,也可以提供自定义的版本改写上述的函数。

问题在于原先的c++无法精确地控制这些默认函数的生成。 比方说,要让类型不能被拷贝,必须将复

制构造函数与赋值操作符声明为private,并不去定义它们。 尝试使用这些未定义的函数会导致编译

期或连结期的错误。 但这种手法并不是一个理想的解决方案。

此外,编译器产生的默认构造函数与用户定义的构造函数无法同时存在。 若用户定义了任何构造函

数,编译器便不会生成默认构造函数; 但有时同时带有上述两者提供的构造函数也是很有用的。 目

前并没有显式指定编译器产生默认构造函数的方法。

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

以下的语法:

struct SomeType

21页 共36页 2011/11/15 下午 13:51

{

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

SomeType(OtherType value);

};

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

struct NonCopyable

{

NonCopyable & operator=(constNonCopyable&) = delete;

NonCopyable(constNonCopyable&) = delete;

NonCopyable() = default;

};

禁止类型以operatornew配置存储器:

struct NonNewable

{

void *operator new(std::size_t) = delete;

};

此种对象只能生成于 stack 中或是当作其他类型的成员,它无法直接配置于 heap 之中,除非使用

了与平台相关,不可移植的手法。(使用placement new 运算符虽然可以在用户自配置的存储器上

调用对象构造函数,但在此例中其他形式的 new 运算符一并被上述的定义 屏蔽("namehiding")

所以也不可行。)

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

struct NoDouble

{

voidf(inti);

voidf(double) = delete;

};

若尝试以double 的形参调用 f(),将会引发编译期错误, 编译器不会自动将double 形参转型为

int 再调用f()。 若要彻底的禁止以非int的形参调用f(),可以将=delete与模板相结合:

struct OnlyInt

{

voidf(inti);

template<classT> voidf(T) = delete;

};

long long int类别

在 32 位系统上,一个long longint 是保有至少 64 个有效比特的整数类别。C99 将这个类别引

入了标准中,目前大多数的 C++ 编译器也支持这种类别。C++11 将把这种类别添加到标准C++

中。

22页 共36页 2011/11/15 下午 13:51

静态assertion

C++提供了两种方法测试assertion(声明):宏assert以及前处理器指令#error。但是这两者对于模版

来说都不合用。宏在运行期测试assertion,而前处理器指令则在前置处理时测试assertion,这时候

模版还未能实例化。所以它们都不适合来测试牵扯到模板实参的相关特性。

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

采取以下的形式:

static_assert( constant-expression, error-message ) ;

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

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

template< classT >

struct Check

{

static_assert( sizeof(int) <= sizeof(T), "T isnotbigenough!" ) ;

} ;

当常数表达式值为false时,编译器会产生相应的错误消息。第一个例子是前处理器指令#error的替

代方案;第二个例子会在每个模板类型Check生时检查assertion

静态assertion在模板之外也是相当有用的。例如,某个算法的实现依赖于long long类别的大小比

int还大,这是标准所不保证的。 这种假设在大多数的系统以及编译器上是有效的,但不是全部。

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

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

struct SomeType { OtherType member; };

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

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

用。

垃圾回收机制

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

C++标准程序库的变更

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

少)新的C++11 核心语言机能。

新的程序库的大部分被定义于C++标准委员会的LibraryTechnicalReport(TR1),于2005年发

布。各式TR1 的完全或部分实现目前提供在命名空间 std::tr1C++11 会将其移置于命名空间std

之下。"However, as TR1 features arebroughtinto the

23页 共36页 2011/11/15 下午 13:51

标准库组件上的升级

目前的标准库能受益于C++11 新增的一些语言特性。举例来说,对于大部份的标准库容器而言,像

是搬移内含大量元素的容器,或是容器之内对元素的搬移,基于右值引用 (Rvaluereference) 

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::mutexstd::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 版本的

24页 共36页 2011/11/15 下午 13:51

多元組型別對所能容納的物件個數會因實作而有所限制,且實作上需要用到大量的巨集技巧。相反

的,C++11 版本的多元組型基本上於對其能容納的物件個數沒有限制。然而,編譯器對於模板實體化

的遞迴深度上的限制仍舊影響了元組型別所能容納的物件個數(這是無法避免的情況); C++11 版本

的多元組型不會把這個值讓使用者知道。

使用不定長參數模板,多元組型別的宣告可以長得像下面這樣:

template <class...Types> class tuple;

底下是一個多元組型別的定義和使用情況:

typedef std::tuple <int, double, long &, const char *> test_tuple;

longlengthy = 12;

test_tuple proof (18, 6.5,lengthy, "Ciao!");

lengthy =std::get<0>(proof);  // 將 proof 的第一個元素賦值給 lengthy(索引從零開始起跳)

std::get<3>(proof) = "Beautiful!";  // 修改 proof 的第四個元素

我們可以定義一個多元組型別物件 proof 而不指定其內容,前提是 proof 裡的元素其型別定義了預

設建構子(default constructor)。此外,以一個多元組型別物件賦值給另一個多元組型別物件是可

能的,但只有在以下情況若這兩個多元組型別相同,則其內含的每一個元素其型別都要定義拷貝建

構子(copyconstructor); 否則的話,賦值操作符右邊的多元組其內含元素的型別必須能轉換成左

邊的多元組其對應的元素型別,又或者賦值操作符左邊的多元組其內含元素的型別必須定義適當的建

構子。

typedef std::tuple< int, double,string    > tuple_1 t1;

typedef std::tuple< char, short , const char * >tuple_2t2 ('X', 2, "Hola!");

t1 = t2 ;// 可行。前兩個元素會作型別轉換,

// 第三個字串元素可由'const char *' 所建構。

多元組類型物件的比較運算是可行的(當它們擁有同樣數量的元素)。此外,C++11 提供兩個表達式

用來檢查多元組類型的一些特性(僅在編譯期做此檢查)

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

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

散列表

在过去,不断有要求想将散列表(无序关系式容器)引进标准库。只因为时间上的限制,散列表才没有

被标准库所采纳。虽然,散列表在最糟情况下(如果出现许多冲突(collision) 的话)在性能上比不

过平衡树。但实际运用上,散列表的表现则较佳。

因为标准委员会还看不到有任何机会能将开放寻址法标准化,所以目前冲突仅能通过链地址法

(linear chaining) 的方式处理。为避免与第三方库发展的散列表发生名称上的冲突,前缀将采用

unordered 而非hash

库将引进四种散列表,其中差别在于底下两个特性是否接受具相同键值的项目(Equivalent

keys),以及是否会将键值映射到相对应的数据 (Associatedvalues)

25页 共36页 2011/11/15 下午 13:51

散列表类型 有无关系值 接受相同键值

std::unordered_set 否 否

std::unordered_multiset 否 是

std::unordered_map 是 否

std::unordered_multimap 是 是

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

begin,end

散列表不需要对现有核心语言做扩展(虽然散列表的实现会利用到C++11 新的语言特性),只会对

头文件 <functional> 做些许扩展,并引入<unordered_set> <unordered_map> 两个头文件。对

于其它现有的类型不会有任何修改。同时,散列表也不会依赖其它标准库的扩展功能。

正则表达式

过去许多或多或少标准化的程序库被创建用来处理正则表达式。有鉴于这些算法的使用非常普遍,因

此标准程序库将会包含他们,并使用各种面向对象语言的潜力。

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

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

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

函数regex_search 是用来搜索样式若要搜索并取代,则要使用函数 regex_replace,该函数会回

传一个新的字符串。算法regex_search 和 regex_replace 接受一个正则表达式(样式)和一个字符

串,并将该样式匹配的情况存储在 struct match_results

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

const char *reg_esp = "[ ,.\\t\\n;:]" ;// 分隔字元列表

std::regex rgx(reg_esp) ; // 'regex' 是樣板類'basic_regex' 以型別為'char' 

//  的參數具現化的實體

std::cmatchmatch ;//'cmatch' 是樣板類 match_results' 以型別為'constchar *'

// '的參數具現化的實體

const char *target = "PolytechnicUniversityof Turin " ;

// 辨別所有被分隔字元所分隔的字

if( regex_search( target, match, rgx ) )

{

// 若此種字存在

const size_t n = match.size();

for( size_ta = 0 ;a <n ;a++ )

{

string str( match[a].first,match[a].second ) ;

cout << str << "\n" ;

}

}

注意双反斜线的使用,因为C++ 将反斜线作为跳脱字符使用。但C++11 raw string可以用来避免

26页 共36页 2011/11/15 下午 13:51

此一问题。库<regex> 不需要改动到现有的头文件,同时也不需要对现有的语言作扩展。

通用智能指针

这些指针是由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_ptrweak_ptr 並不擁有它所指

向的物件,因此不影響該物件的銷毀與否。

底下是一個 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_ptrunique_ptr 提供auto_ptr 大部

份特性,唯一的例外是auto_ptr 的不安全、隱性的左值搬移。不像auto_ptrunique_ptr 可以存

放在C++11 提出的那些能察觉搬移动作的容器之中。

可扩展的随机数功能

标准库允许使用rand函数来生成伪随机数。不过其算法则取决于各程序库开法者。C++ 直接从C

继承了这部份,但是 C++11 将会提供产生伪乱数的新方法。

C++11 的随机数功能分为两部分: 第一,一个乱数生引擎,其中包含该生引擎的状态,用来产

生乱数。第二,一个分布,这可以用来决定产生乱数的范围,也可以决定以何种分布方式产生乱数。

乱数生成对象即是由乱数生引擎和分布所构成。

不同于 标准库的 rand; 針對產生亂數的機制,C++11 將會提供三種演算法,每一種演算法都有其

強項和弱項:

樣板類 整數/浮點數 品質 速度 狀態數*

linear_congruential 整數 低 中等 1

27页 共36页 2011/11/15 下午 13:51

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); // 以離散型均勻分佈方式產生int 亂數,範圍

std::mt19937engine; // 建立亂數生引擎

autogenerator =std::bind(distribution,engine); // 利用 bind 將亂數生引擎和分布組合一個亂數

int random =generator(); // 產生亂數

包装引用

我們可以透過實體化樣板類reference_wrapper 得到一個包装引用(wrapper reference)。包装引

用類似於一般的引用。對於任意物件,我們可以透過模板類 ref 得到一個包装引用(至於 constant

reference 則可透過cref 得到)

當樣板函式需要形參的引用而非其拷貝,這時包装引用就能派上用場:

// 此函數將得到形參'r' 的引用並對加一

voidf (int &r)  {r++; }

// 樣板函式

template<class F, classP> voidg (F f, Pt)  {f(t); }

int main()

{

inti = 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> 之中,而非通过扩展语言来得到这项功能。

多态函数对象包装器

针对函数对象的多态包装器(又称多态函数对象包装器)在语义和语法上和函数指针相似,但不像函数

指针那么狭隘。只要能被调用,且其实参能与包装器兼容的都能以多态函数对象包装器称之(函数指

针,成员函数指针或仿函数)

28页 共36页 2011/11/15 下午 13:51

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

std::function<int (int, int)>func;  // 利用樣板類'function'

// 建立包裝器

std::plus<int> add;//'plus' 被宣告為'template<class T> Tplus( T, T) ;'

//  因此 'add' 的型別是'int add(intx,inty )'

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

func2 = &adjacent ;  // 可行。'adjacent' 的型參和回返值型別可透過型別轉換進而與 'func2' 相符

structTest {

booloperator()(shortx, shorty);

};

Test car;

func =std::ref(car);  // 樣板類'std::ref' 回傳一個struct'car'

// 其成員函式'operator()' 的包裝

}

func =func2;  // 可行。'func2' 的型參和回返值型別可透過型別轉換進而與'func' 相符

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

用于元编程的类别属性

对于那些能自行创建或修改本身或其它程序的程序,我们称之为元编程。这种行为可以发生在编译或

运行期。C++ 标准委员会已经决定引进一组由模板实现的库,程序员可利用此一库于编译期进行元编

程。

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

template<intB, intN>

struct Pow {

//recursive call and recombination.

enum{value =B*Pow<B, N-1>::value };

};

template< int B >

struct Pow<B, 0> {

//''N==0''condition of termination.

enum{value = 1 };

};

int quartic_of_three =Pow<3, 4>::value;

许多算法能作用在不同的数据类别;C++ 模板支持泛型,这使得代码能更紧凑和有用。然而,算法经

常会需要目前作用的数据类别的信息。这种信息可以通过类别属性(typetraits) 於模板實體化時

將該資訊萃取出來。

29页 共36页 2011/11/15 下午 13:51

型別屬性能識別一個物件的種類和有關一個型別 (class)(或 struct) 的特徵。標頭檔

<type_traits> 描述了我们能识别那些特征。

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

(algorithm.do_it)

// 演算法一

template< bool B > struct Algorithm {

template<class T1, classT2> int do_it (T1 &,T2 &)  { /*...*/ }

};

// 演算法二

template<> struct Algorithm<true> {

template<class T1, classT2> int do_it (T1, T2)  { /*...*/ }

};

// 根據給定的型別,實體化之後的'elaborate' 會選擇演算法一或二

template<class T1, class T2>

int elaborate (T1 A, T2B)

{

// 若 T1 int T1 float,選用演算法二

// 其它情況選用演算法一

returnAlgorithm<std::is_integral<T1>::value &&std::is_floating_point<T2>::value

}

通过定义在 <type_transform> 的类别属性,自定的类别转换是可能的(在模板中,static_cast 

const_cast 无法适用所有情况)

此种编程技巧能写出优美、简洁的代码然而除错是此种编程技巧的弱处编译期的错误消息让人不

知所云,运行期的除错更是困难。

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

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

来说:

struct Clear {

int   operator()(int);   // 參數與回返值的型別相同

doubleoperator()(double);// 參數與回返值的型別相同

};

template <classObj>

classCalculus {

public:

template<class Arg>Argoperator()(Arg&a) const

{

returnmember(a);

}

private:

Obj member;

};

实体化样板类Calculus<Clear>Calculus 的仿函数其回返值总是和Clear 的仿函数其回返值具有

30页 共36页 2011/11/15 下午 13:51

相同的类别。然而,若给定类型Confused:

struct Confused {

doubleoperator()(int);   // 參數與回返值的型別不相同

int   operator()(double);  // 參數與回返值的型別不相同

};

企图实体化样板类Calculus<Confused> 将导致Calculus 的仿函数其回返值和类型Confused 的仿

函数其回返值有不同的类别。对于 int double 之间的转换,编译器将给出警告。

模板std::result_of TR1 引进且被C++11 所采纳,可允许我们决定和使用一个仿函数其回返值

的类别。底下,CalculusVer2 对象使用std::result_of 对象来推导其仿函数的回返值类别:

template< classObj >

classCalculusVer2 {

public:

template<class Arg>

typename std::result_of<Obj(Arg)>::type operator()(Arg&a) const

{

returnmember(a);

}

private:

Obj member;

};

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

模板std::result_of TR1 C++11 有一点不同。TR1 的版本允许实现在特殊情况下,可以无法

决定一个函数调用其回返值类别。然而,因为C++11支持了decltype,实现被要求在所有情况下,皆

能计算出回返值类别。


0 0
原创粉丝点击