因为模版元程序的执行完全是在编译期,由auto定

作者: 编程  发布:2019-08-29

C 11标准教程,11标准教程

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年为止的提案,忽略新的提案[1]。最终于2011年8月12日公布,并于2011年9月出版。

2012年2月28日的国际标准草案(N3376)是最接近于现行标准的草案,差异仅有编辑上的修正。

像C 这样的编程语言,通过一种演化的的过程来发展其定义。这个过程不可避免地将引发与现有代码的兼容问题,在C 的发展过程中偶尔会发生。不过根据Bjarne Stroustrup(C 的创始人并且是委员会的一员)表示,新的标准将几乎100%兼容于现有标准。

目录
1候选变更
2C 核心语言的扩充
3核心语言的运行期表现强化
3.1右值引用和 move 语义
3.2泛化的常数表示式
3.3对POD定义的修正
4核心语言建构期表现的加强
4.1外部模板
5核心语言使用性的加强
5.1初始化列表
5.2统一的初始化
5.3类型推导
5.4以范围为基础的 for 循环
5.5Lambda函数与表示式
5.6另一种的函数语法
5.7对象建构的改良
5.8显式虚函数重载
5.9空指针
5.10强类型枚举
5.11角括号
5.12显式类型转换子
5.13模板的别名
5.14无限制的unions
6核心语言能力的提升
6.1变长参数模板
6.2新的字符串字面值
6.3用户自定义的字面值
6.4多任务存储器模型
6.5thread-local的存储期限
6.6使用或禁用对象的默认函数
6.7long 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用于计算函数对象返回类型的统一方法
8已被移除或是不包含在 C 11 标准的特色
9被移除或废弃的特色
10编译器实现
11关系项目
12参考资料
12.1C 标准委员会文件
12.2文章
13外部链接
候选变更
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<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>()。

bool is_r_value(int &&) { return true; }
bool is_r_value(const int &) { return false; }

void test(int && i)
{
is_r_value(i); // i 為具名變數,即使被宣告成右值也不會被認定是右值。
is_r_value(std::move<int>(i)); // 使用 std::move<T>() 取得右值。
}
由于右值引用的用语特性以及对于左值引用(L-value references;regular references)的某些用语修正,右值引用允许开发者提供完美转发 (perfect function forwarding)。当与变长参数模板结合,这项能力允许函数模板能够完美地转送引数给其他接受这些特定引数的函数。最大的用处在于转送构造函数参数,创造出能够自动为这些特定引数调用正确建构式的工厂函数(factory function)。

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

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

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。第二点,函数的内容必须依照 "returnexpr" 的形式。第三点,在引数取代后,expr 必须是个常数表示式。这些常数表示式只能够调用其他被定义为 constexpr 的函数,或是其他常数表示式的数据变量。 最后一点,有着这样标签的函数直到在该编译单元内被定义之前是不能够被调用的。

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

constexpr double forceOfGravity = 9.8;
constexpr double moonGravity = 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兼容的对象布局(object layout)。然而,C 03的规则太严苛了。

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

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

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

极简的默认建构式。这可以使用默认建构式语法,例如SomeConstructor() = default;
极简的复制建构式,可使用默认语法(default syntax)
极简的赋值运算符,可使用默认语法(default syntax)
极简的解构式,不可以是虚拟的(virtual)
一个标准布局(standard-layout)的类型或结构符合以下定义:

只有非静态的(non-static)数据成员,且这些成员也是符合标准布局的类型
对所有non-static成员有相同的访问控制(public, private, protected)
没有虚函数
没有虚拟基类
只有符合标准布局的基类
没有和第一个定义的non-static成员相同类型的基类
若非没有带有non-static成员的基类,就是最底层(继承最末位)的类型没有non-static数据成员而且至多一个带有non-static成员的基类。基本上,在该类型的继承体系中只会有一个类型带有non-static成员。
核心语言建构期表现的加强
外部模板
在标准C 中,只要在编译单元内遇到被完整定义的模板,编译器都必须将其实例化(instantiate)。这会大大增加编译时间,特别是模板在许多编译单元内使用相同的参数实例化。看起来没有办法告诉C 不要引发模板的实例化。

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

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

extern template class std::vector<MyClass>;
这样就告诉编译器不要在该编译单元内将该模板实例化。

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

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

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

class SequenceClass
{
public:
SequenceClass(std::initializer_list<int> list);
};
这将允许SequenceClass由一连串的整数构造,就像:

SequenceClass someVar = {1, 4, 5, 6};
这个构造函数是种特殊的构造函数,称作初始化列表构造函数。有着这种构造函数的类型在统一初始化的时候会被特别对待。

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

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

void FunctionName(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
{
int x;
float y;
};

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

private:
int x;
float y;
};

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

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

struct IdString
{
std::string name;
int identifier;
};

IdString var3{"SomeName", 4};
该语法将会使用 const char * 参数初始化 std::string 。你也可以做像下面的事:

IdString GetString()
{
return {"SomeName", 4}; // 注意這裡不需要明確的型別
}
统一初始化不会取代建构式语法。仍然会有需要用到建构式语法的时候。如果一个类型拥有初始化串行建构式(TypeName(initializer_list<SomeType>);),而初始化串行符合 sequence 建构式的类型,那么它比其他形式的建构式的优先权都来的高。C 11 版本的std::vector 将会有初始化串行建构式。这表示:

std::vector<int> theVec{4};
这将会调用初始化串行建构式,而不是调用std::vector只接受一个尺寸参数产生相应尺寸 vector 的建构式。要使用这个建构式,用户必须直接使用标准的建构式语法。

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

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

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

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

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

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

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

for (vector<int>::const_iterator itr = myvec.cbegin(); itr != myvec.cend(); itr)
而可以用更简短的

for (auto itr = myvec.cbegin(); itr != myvec.cend(); itr)
这项差异随着程序员开始嵌套容器而更为显著,虽然在这种情况下 typedef 是一个减少代码的好方法。

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

#include <vector>

int main()
{
const std::vector<int> v(1);
auto a = v[0];// a 為 int 型別
decltype(v[0]) b = 0; // b 為 const int& 型別,即
// std::vector<int>::operator[](size_type)const 的回返型別
auto c = 0; // c 為 int 型別
auto d = c; // d 為 int 型別
decltype(c) e; // e 為 int 型別,c 實體的型別
decltype((c)) f = e; // f 為 int& 型別,因為(c)是左值
decltype(0) g; // g為int型別,因為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, int y) { return x y; }
这个不具名函数的回返类型是 decltype(x y)。只有在 lambda 函数符合"return expression"的形式下,它的回返类型才能被忽略。在前述的情况下,lambda 函数仅能为一个述句。

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

[](int x, int y) -> int { int z = x y; return z x; }
本例中,一个暂时的变量 z 被创建用来存储中间结果。如同一般的函数,z 的值不会保留到下一次该不具名函数再次被调用时。

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

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

[] // 沒有定義任何變數。使用未定義變數會導致錯誤。
[x, &y] // x 以傳值方式傳入(預設),y 以傳參考方式傳入。
[&] // 任何被使用到的外部變數皆隱式地以參考方式加以引用。
[=] // 任何被使用到的外部變數皆隱式地以傳值方式加以引用。
[&, x] // x 顯示地以傳值方式加以引用。其餘變數以參考方式加以引用。
[=, &z] // z 顯示地以參考方式加以引用。其餘變數以傳值方式加以引用。
closure 被定义与使用如下:

std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&total](int x) {
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(), [&](int x) {
total = x;
});
变量传入 lambda 函数的方式可能随实做有所变化,一般期望的方法是 lambda 函数能保留其作用域函数的 stack 指针,借此访问区域变量。

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

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

int total = 0;
int value = 5;
[&, value](int x) { total = (x * value); };
total 是传参考的方式传入 lambda 函数,而 value 则是传值。

若一个 lambda 函数被定义于某类型的成员函数中,会被当作该类型的 friend。像这样的 lambda 函数可以使用该类型对象的参考,并且能够访问其内部的成员。

[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); };
这只有当该 lambda 函数创建的作用域是在 SomeType 的成员函数内部时才能运作。

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

[this]() { this->SomePrivateMemberFunction(); };
若是 lambda 函数使用 [&] 或是 [=] 的形式,this在 lambda 函数即为可见。

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

auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };
auto myOnheapLambdaFunc = 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 除了 C 的基础语法外,又扩充额外的语法。 然而,当 C 变得更为复杂时,它暴露出许多语法上的限制, 特别是针对函数模板的声明。 下面的示例,不是合法的 C 03:

template< typename LHS, typename RHS>
Ret AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs rhs;} //Ret的型別必須是(lhs rhs)的型別
Ret 的类型由 LHS与RHS相加之后的结果的类型来决定。 即使使用 C 11 新加入的 decltype 来声明 AddingFunc 的返回类型,依然不可行。

template< typename LHS, typename RHS>
decltype(lhs rhs) AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs

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

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

template< typename LHS, typename RHS>
auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs rhs) {return lhs rhs;}
这种语法也能套用到一般的函数定义与声明:

struct SomeStruct
{
auto FuncName(int x, int y) -> int;
};

auto SomeStruct::FuncName(int x, int y) -> int
{
return x y;
}
关键字 auto 的使用与其在自动类型推导代表不同的意义。

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

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

class SomeType {
int number;
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)。 而派生类的新加入的建构式也不可以和继承而来的基底建构式有相同的函数签名,因为这相当于重复声明。

语法如下:

class BaseClass
{
public:
BaseClass(int iValue);
};

class DerivedClass : public BaseClass
{
public:
using BaseClass::BaseClass;
};
此语法等同于 DerivedClass 声明一个DerivedClass(int) 的建构式。 同时也因为 DerivedClass 有了一个继承而来的建构式,所以不会有默认建构式。

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

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

private:
int iValue = 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 同名且拥有相同签名的虚函数。

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

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

struct Base {
virtual void some_func(float);
};

struct Derived : Base {
virtual void some_func(int) override; // 錯誤格式: Derive::some_func 並沒有 override Base::some_func
virtual void some_func(float) override; // OK:顯式改寫
};
编译器会检查基底类型是否存在一虚拟函数,与派生类中带有声明override 的虚拟函数,有相同的函数签名(signature);若不存在,则会回报错误。

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

struct Base1 final { };

struct Derived1 : Base1 { }; // 錯誤格式: class Base1 以標明為 final

struct Base2 {
virtual void f() final;
};

struct Derived2 : Base2 {
void f(); // 錯誤格式: Base2::f 以標明為 final
};
以上的示例中,virtual void f() final;声明一新的虚拟函数,同时也表明禁止派生函数改写原虚拟函数。

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

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

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

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

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

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

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 允许为传统的枚举指定使用类型:

enum Enum3 : unsigned long {Val1 = 1, Val2};
枚举名 Val1 定义于 Enum3 的枚举范围中(Enum3::Val1),但为了兼容性, Val1 仍然可以于一般的范围中单独使用。

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

enum Enum1; // 不合法的 C 與 C 11; 無法判別大小
enum Enum2 : unsigned int; // 合法的 C 11
enum class Enum3; // 合法的 C 11,列舉類別使用預設型別 int
enum class Enum4: unsigned int; // 合法的 C 11
enum Enum2 : unsigned short; // 不合法的 C 11,Enum2 已被聲明為 unsigned int
角括号
标准 C 的剖析器一律将 ">>" 视为右移运算符。 但在样板定义式中,绝大多数的场合其实都代表两个连续右角括号。 为了避免剖析器误判,撰码时不能把右角括号连着写。

C 11 变更了剖析器的解读规则;当遇到连续的右角括号时,优先解析右角括号为样板引数的退出符号。 如果解读过程中出现普通括号("(" 与 ")"),这条规则产生变化:

template<bool bTest> SomeType;
std::vector<SomeType<1>2>> x1; // 解讀為 std::vector of "SomeType<true> 2>",
// 非法的表示式, 整數 1 被轉換為 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, typename second, int third>
class SomeType;

template< typename second>
typedef SomeType<OtherType, second, 5> TypedefName; // 在C 是不合法的
这不能够通过编译。

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

template< typename first, typename second, int third>
class SomeType;

template< typename second>
using TypedefName = SomeType<OtherType, second, 5>;
using 也能在 C 11 中定义一般类型的别名,等同 typedef:

typedef void (*PFD)(double); // 傳統語法
using PFD = void (*)(double); // 新增語法
无限制的unions
在标准 C 中,并非任意的类型都能做为 union 的成员。比方说,带有 non-trivial 构造函数的类型就不能是 union 的成员。在新的标准里,移除了所有对 union 的使用限制,除了其成员仍然不能是引用类型。 这一改变使得 union 更强大,更有用,也易于使用。[1]

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

struct point
{
point() {}
point(int x, int y): x_(x), y_(y) {}
int x_, y_;
};
union
{
int z;
double w;
point p; // 不合法的 C ; point 有一 non-trivial 建構式
// 合法的 C 11
};
这一改变仅放宽 union 的使用限制,不会影响既有的旧代码。

核心语言能力的提升
这些机能提供了C 语言能够做一些事情是以前所不能达成的,或是在以前需要繁琐的写法、要求一些不可移植的程序库。

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

template<typename... Values> class tuple;
模板类 tuple 的对象,能接受不限个数的 typename 作为它的模板形参:

class tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> someInstanceName;
实参的个数也可以是 0,所以 class tuple<> someInstanceName 这样的定义也是可以的。

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

template<typename First, typename... Rest> class tuple;
变长参数模板也能运用到模板函数上。 传统 C 中的 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) != '%')
throw std::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 value和 Args... args。 直到 args... 变为空参数,则会与简单的printf(const char *s) 形成匹配,退出递归。

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

template<>
struct count<> {
static const int value = 0;
};

template<typename T, typename... Args>
struct count<T, Args...> {
static const int value = 1 count<Args...>::value;
};
虽然没有一个简洁的机制能够对变长参数模板中的值进行迭代,但使用运算符"..."还能在代码各处对参数包施以更复杂的展开操作。举例来说,一个模板类的定义:

template <typename... BaseClasses> class ClassName : public BaseClasses...
{
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(Args&&... params)
{
return tr1::shared_ptr<TypeToConstruct>(new TypeToConstruct(std::forward<Args>(params)...));
}
}
参数包 parms 可展开为 TypeToConstruct 构造函数的形参。 表达式std::forward<Args>(params) 可将形参的类别信息保留(利用右值引用),传入构造函数。 而运算符"..."则能将前述的表达式套用到每一个参数包中的参数。这种工厂函数(factory function)的手法, 使用std::shared_ptr 管理配置对象的存储器,避免了不当使用所产生的存储器泄漏(memory leaks)。

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

template<typename ...Args> struct SomeStruct
{
static const int size = sizeof...(Args);
}
SomeStruct<Type1, Type2>::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(未加工的)字符串字面值:

R"(The String Data Stuff " )"
R"delimiter(The String Data Stuff " )delimiter"
在第一个例子中,任何包含在( )括号(标准已经从[]改为())当中的都是字符串的一部分。其中"和字符不需要经过跳脱(escaped)。在第二个例子中,"delimiter(开始字符串,只有在遇到)delimiter"才代表退出。其中delimiter可以是任意的字符串,能够允许用户在未加工的字符串字面值中使用)字符。 未加工的字符串字面值能够和宽字面值或是Unicode字面值结合:

u8R"XXX(I'm a "raw UTF-8" string.)XXX"
uR"*@(This is a "raw UTF-16" string.)*@"
UR"(This is a "raw UTF-32" string.)"
用户自定义的字面值
标准C 提供了数种字面值。字符"12.5"是能够被编译器解释为数值12.5的double类别字面值。然而,加上"f"的后置,像是"12.5f",则会产生数值为12.5的float类别字面值。在C 规范中字面值的后置是固定的,而且C 代码并不允许创立新的字面后置。

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

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

多任务存储器模型
参见:内存模型(computing)
C 标准委员会计划统一对多线程编程的支持。

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

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

thread-local的存储期限
在多线程环境下,让各线程拥有各自的变量是很普遍的。这已经存在于函数的区域变量,但是对于全局和静态变量都还不行。

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

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

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

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

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

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

struct SomeType
{
SomeType() = default; // 預設建構式的顯式聲明
SomeType(OtherType value);
};
另一方面,也可以禁止编译器自动产生某些函数。如下面的例子,类型不可复制:

struct NonCopyable
{
NonCopyable & operator=(const NonCopyable&) = delete;
NonCopyable(const NonCopyable&) = delete;
NonCopyable() = default;
};
禁止类型以operator new配置存储器:

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

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

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

struct OnlyInt
{
void f(int i);
template<class T> void f(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( constant-expression, error-message ) ;
这里有一些如何使用static_assert的例子:

static_assert( 3.14 < GREEKPI && GREEKPI < 3.15, "GREEKPI is inaccurate!" ) ;
template< class T >
struct Check
{
static_assert( sizeof(int) <= sizeof(T), "T is not big enough!" ) ;
} ;
当常数表达式值为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 标准委员会的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 版本的多元组型不会把这个值让用户知道。

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

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); // 將 proof 的第一個元素賦值給 lengthy (索引從零開始起跳)
std::get<3>(proof) = " Beautiful!"; // 修改 proof 的第四個元素
我们可以定义一个多元组类别对象 proof 而不指定其内容,前提是 proof 里的元素其类别定义了默认构造函数 (default constructor)。此外,以一个多元组类别对象赋值给另一个多元组类别对象是可能的,但只有在以下情况: 若这两个多元组类别相同,则其内含的每一个元素其类别都要定义拷贝构造函数 (copy constructor); 否则的话,赋值操作符右边的多元组其内含元素的类别必须能转换成左边的多元组其对应的元素类别,又或者赋值操作符左边的多元组其内含元素的类别必须定义适当的构造函数。

typedef std::tuple< int , double, string > tuple_1 t1;
typedef std::tuple< char, short , const char * > tuple_2 t2 ('X', 2, "Hola!");
t1 = t2 ; // 可行。前兩個元素會作型別轉換,
// 第三個字串元素可由 'const char *' 所建構。
多元组类型对象的比较运算是可行的(当它们拥有同样数量的元素)。此外,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>头文件,由几个新的类型所组成:

正则表达式(样式)以样板类 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::cmatch match ; // 'cmatch' 是樣板類 match_results' 以型別為 'const char *'
// '的參數具現化的實體
const char *target = "Polytechnic University of Turin " ;

// 辨別所有被分隔字元所分隔的字
if( regex_search( target, match, rgx ) )
{
// 若此種字存在

const size_t n = match.size();
for( size_t a = 0 ; a < n ; a )
{
string str( match[a].first, match[a].second ) ;
cout << str << "n" ;
}
}
注意双反斜线的使用,因为 C 将反斜线作为跳脱字符使用。但 C 11 的raw string可以用来避免此一问题。库 <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_ptr。weak_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_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); // 以離散型均勻分佈方式產生 int 亂數,範圍落在 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 加一
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 标准委员会已经决定引进一组由模板实现的库,程序员可利用此一库于编译期进行元编程。

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

template<int B, int N>
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 模板支持泛型,这使得代码能更紧凑和有用。然而,算法经常会需要目前作用的数据类别的信息。这种信息可以通过类别属性 (type traits) 于模板实体化时将该信息萃取出来。

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

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

// 演算法一
template< bool B > struct Algorithm {
template<class T1, class T2> int do_it (T1 &, T2 &) { /*...*/ }
};

// 演算法二
template<> struct Algorithm<true> {
template<class T1, class T2> int do_it (T1, T2) { /*...*/ }
};

// 根據給定的型別,實體化之後的 'elaborate' 會選擇演算法一或二
template<class T1, class T2>
int elaborate (T1 A, T2 B)
{
// 若 T1 為 int 且 T1 為 float,選用演算法二
// 其它情況選用演算法一
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); // 參數與回返值的型別相同
double operator()(double); // 參數與回返值的型別相同
};

template <class Obj>
class Calculus {
public:
template<class Arg> Arg operator()(Arg& a) const
{
return member(a);
}
private:
Obj member;
};
实体化样板类 Calculus<Clear>,Calculus 的仿函数其回返值总是和 Clear 的仿函数其回返值具有相同的类别。然而,若给定类型Confused:

struct Confused {
double operator()(int); // 參數與回返值的型別不相同
int operator()(double); // 參數與回返值的型別不相同
};
企图实体化样板类 Calculus<Confused> 将导致 Calculus 的仿函数其回返值和类型 Confused 的仿函数其回返值有不同的类别。对于int 和 double 之间的转换,编译器将给出警告。

模板 std::result_of 被TR1 引进且被 C 11 所采纳,可允许我们决定和使用一个仿函数其回返值的类别。底下,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,实现被要求在所有情况下,皆能计算出回返值类别。

已被移除或是不包含在 C 11 标准的特色
预计由 Technical Report 提供支持:

模块
十进制类别
数学专用函数
延后讨论:

Concepts (概念 (C ))
更完整或必备的垃圾回收支持
Reflection
Macro Scopes
被移除或废弃的特色
循序点 (sequence point),这个术语正被更为易懂的描述所取代。一个运算可以发生 (is sequenced before) 在另一个运算之前; 又或者两个运算彼此之间没有顺序关系 (are unsequenced)。
export
exception specifications
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
GCC 4.6 : Status of Experimental C 0x Support in GCC 4.6
关系项目
C Technical Report 1
C11,C 编程语言的最新标准
C 1y,计划中的 C 标准

 

原文链接:

C 11,先前被称作C 0x,即ISO/IEC 14882:2011,是目前的C 编程语言的正式标准。它取代第二版标准ISO/IEC 14882:2003(第一...

移动语义和右值引用

避免无意义的拷贝和暂存变量。

The programmer, however, has to specify that a move is possible unless a temporary is used.

std::move(),位于<utility> 。std::move(x)本身不会进行移动操作,只是将临时变量转换为一个右值引用(X &&x,使用双&声明,表明右值是可以修改的),表明此临时变量x将不会再使用了,可以将它的内容据为己有(不需要拷贝)。

移动之后不再使用原来的变量,原来的变量有效但是状态未定义。

void foo(X &x);//只接受左值

void foo(const X &x);//接受左值和右值

void foo(X &&x);//接受右值

C 11模版元编程

1.概述

 

  模版元编程(template metaprogram)是C 中最复杂也是威力最强大的编程范式,它是一种可以创建和操纵程序的程序。模版元编程完全不同于普通的运行期程序,它很独特,因为模版元程序的执行完全是在编译期,并且模版元程序操纵的数据不能是运行时变量,只能是编译期常量,不可修改,另外它用到的语法元素也是相当有限,不能使用运行期的一些语法,比如if-else,for等语句都不能用。因此,模版元编程需要很多技巧,常常需要类型重定义、枚举常量、继承、模板偏特化等方法来配合,因此编写模版元编程比较复杂也比较困难。

 

  现在C 11新增了一些模版元相关的特性,不仅可以让我们编写模版元程序变得更容易,还进一步增强了泛型编程的能力,比如type_traits让我们不必再重复发明轮子了,给我们提供了大量便利的元函数,还提供了可变模板参数和tuple,让模版元编程“如虎添翼”。本文将向读者展示C 11中模版元编程常用的技巧和具体应用。

 

2.模版元基本概念

 

  模版元程序由元数据和元函数组成,元数据就是元编程可以操作的数据,即C 编译器在编译期可以操作的数据。元数据不是运行期变量,只能是编译期常量,不能修改,常见的元数据有enum枚举常量、静态常量、基本类型和自定义类型等。

 

  元函数是模板元编程中用于操作处理元数据的“构件”,可以在编译期被“调用”,因为它的功能和形式和运行时的函数类似,而被称为元函数,它是元编程中最重要的构件。元函数实际上表现为C 的一个类、模板类或模板函数,它的通常形式如下:

 

template<int N, int M>

struct meta_func

{

    static const value = N M;

}

  调用元函数获取value值:cout<<meta_func<1, 2>::value<<endl;

 

  meta_func的执行过程是在编译期完成的,实际执行程序时,是没有计算动作而是直接使用编译期的计算结果的。元函数只处理元数据,元数据是编译期常量和类型,所以下面的代码是编译不过的:

 

int i = 1, j = 2;

meta_func<i, j>::value; //错误,元函数无法处理运行时普通数据

  模板元编程产生的源程序是在编译期执行的程序,因此它首先要遵循C 和模板的语法,但是它操作的对象不是运行时普通的变量,因此不能使用运行时的C 关键字(如if、else、for),可用的语法元素相当有限,最常用的是:

 

enum、static const,用来定义编译期的整数常量;

typedef/using,用于定义元数据;

T、Args...,声明元数据类型;

template,主要用于定义元函数;

"::",域运算符,用于解析类型作用域获取计算结果(元数据)。

如果模板元编程中需要if-else、for等逻辑时该怎么办呢?

 

模板元中的if-else可以通过type_traits来实现,它不仅仅可以在编译期做判断,还可以做计算、查询、转换和选择。

 

模板元中的for等逻辑可以通过递归、重载、和模板特化(偏特化)等方法实现。

 

下面来看看C 11提供的模版元基础库type_traits。

 

3.type_traits

 

  type_traits是C 11提供的模板元基础库,通过type_traits可以实现在编译期计算、查询、判断、转换和选择,提供了模板元编程需要的一些常用元函数。下面来看看一些基本的type_traits的基本用法。

 

  最简单的一个type_traits是定义编译期常量的元函数integral_constant,它的定义如下:

 

template< class T, T v >

struct integral_constant;

  借助这个简单的trait,我们可以很方便地定义编译期常量,比如定义一个值为1的int常量可以这样定义:

 

using one_type = std::integral_constant<int, 1>;

或者

 

template<class T>

struct one_type : std::integral_constant<int, 1>{};

  获取常量则通过one_type::value来获取,这种定义编译期常量的方式相比C 98/03要简单,在C 98/03中定义编译期常量一般是这样定义的:

 

 

template<class T>

struct one_type

{

    enum{value = 1};

};

 

template<class T>

struct one_type

{

    static const int value = 1;

};

 

  可以看到,通过C 11的type_traits提供的一个简单的integral_constant就可以很方便的定义编译期常量,而无需再去通过定义enum和static const变量方式去定义编译期常量了,这也为定义编译期常量提供了另外一种方法。C 11的type_traits已经提供了编译期的true和false,是通过integral_constant来定义的:

 

typedef  integral_constant<bool, true> true_type;

typedef  integral_constant<bool, false> false_type;

  除了这些基本的元函数之外,type_traits还提供了丰富的元函数,比如用于编译期判断的元函数:

 

 

 

  这只是列举一小部分的type_traits元函数,type_traits提供了上百个方便的元函数,读者可以参考

 

 

#include <iostream>

#include <type_traits>

 

int main() {

  std::cout << "int: " << std::is_const<int>::value << std::endl;

  std::cout << "const int: " << std::is_const<const int>::value << std::endl;

 

  //判断类型是否相同

  std::cout<< std::is_same<int, int>::value<<"n";// true

  std::cout<< std::is_same<int, unsignedint>::value<<"n";// false

 

  //添加、移除const

  cout << std::is_same<const int, add_const<int>::type>::value << endl;

  cout << std::is_same<int, remove_const<const int>::type>::value << endl;

 

  //添加引用

  cout << std::is_same<int&, add_lvalue_reference<int>::type>::value << endl;

  cout << std::is_same<int&&, add_rvalue_reference<int>::type>::value << endl;

 

  //取公共类型

  typedef std::common_type<unsigned char, short, int>::type NumericType;

  cout << std::is_same<int, NumericType>::value << endl;

 

  return 0;

}

 

  type_traits还提供了编译期选择traits:std::conditional,它在编译期根据一个判断式选择两个类型中的一个,和条件表达式的语义类似,类似于一个三元表达式。它的原型是:

 

template< bool B, class T, class F >

struct conditional;

用法比较简单:

 

 

#include <iostream>

#include <type_traits>

 

int main() 

{

    typedef std::conditional<true,int,float>::type A;               // int

    typedef std::conditional<false,int,float>::type B;              // float

 

    typedef std::conditional<(sizeof(long long) >sizeof(long double)),

    long long, long double>::type max_size_t;

 

    cout<<typeid(max_size_t).name()<<endl;  //long double

}

 

  另外一个常用的type_traits是std::decay(朽化),它对于普通类型来说std::decay(朽化)是移除引用和cv符,大大简化了我们的书写。除了普通类型之外,std::decay还可以用于数组和函数,具体的转换规则是这样的:

 

  先移除T类型的引用,得到类型U,U定义为remove_reference<T>::type。

 

如果is_array<U>::value为 true,修改类型type为remove_extent<U>::type *。

否则,如果is_function<U>::value为 true,修改类型type将为add_pointer<U>::type。

否则,修改类型type为 remove_cv<U>::type。

std::decay的基本用法:

 

 

typedef std::decay<int>::type A;           // int

typedef std::decay<int&>::type B;          // int

typedef std::decay<int&&>::type C;         // int

typedef std::decay<constint&>::type D;    // int

typedef std::decay<int[2]>::type E;        // int*

typedef std::decay<int(int)>::type F;      // int(*)(int)

 

  std::decay除了移除普通类型的cv符的作用之外,还可以将函数类型转换为函数指针类型,从而将函数指针变量保存起来,以便在后面延迟执行,比如下面的例子。

 

 

template<typename F>

struct SimpFunction

{

    using FnType = typename std::decay<F>::type;//先移除引用再添加指针

 

    SimpFunction(F& f) : m_fn(f){}

 

    void Run()

    {

        m_fn();

    }

 

    FnType m_fn;

};

 

  如果要保存输入的函数,则先要获取函数对应的函数指针类型,这时就可以用std::decay来获取函数指针类型了,using FnType = typename std::decay<F>::type;实现函数指针类型的定义。type_traits还提供了获取可调用对象返回类型的元函数:std::result_of,它的基本用法:

 

 

int fn(int) {return int();}                            // function

typedef int(&fn_ref)(int);                             // function reference

typedef int(*fn_ptr)(int);                             // function pointer

struct fn_class { int operator()(int i){return i;} };  // function-like class

 

int main() {

  typedef std::result_of<decltype(fn)&(int)>::type A;  // int

  typedef std::result_of<fn_ref(int)>::type B;         // int

  typedef std::result_of<fn_ptr(int)>::type C;         // int

  typedef std::result_of<fn_class(int)>::type D;       // int

}

 

  type_traits还提供了一个很有用的元函数std::enable_if,它利用SFINAE(substitude failure is not an error)特性,根据条件选择重载函数的元函数std::enable_if,它的原型是:

 

template<bool B, class T = void> struct enable_if;

 

  根据enable_if的字面意思就可以知道,它使得函数在判断条件B仅仅为true时才有效,它的基本用法:

 

 

template <class T>

typename std::enable_if<std::is_arithmetic<T>::value, T>::type foo(T t)

{

    return t;

}

auto r = foo(1); //返回整数1

auto r1 = foo(1.2); //返回浮点数1.2

auto r2 = foo(“test”); //compile error

 

  在上面的例子中对模板参数T做了限定,即只能是arithmetic(整型和浮点型)类型,如果为非arithmetic类型,则编译不通过,因为std::enable_if只对满足判断式条件的函数有效,对其他函数无效。

 

  可以通过enable_if来实现编译期的if-else逻辑,比如下面的例子通过enable_if和条件判断式来将入参分为两大类,从而满足所有的入参类型:

 

 

template <class T>

typename std::enable_if<std::is_arithmetic<T>::value, int>::type foo1(T t)

{

    cout << t << endl;

    return 0;

}

 

template <class T>

typename std::enable_if<!std::is_arithmetic<T>::value, int>::type foo1(T &t)

{

    cout << typeid(T).name() << endl;

    return 1;

}

 

  对于arithmetic类型的入参则返回0,对于非arithmetic的类型则返回1,通过arithmetic将所有的入参类型分成了两大类进行处理。从上面的例子还可以看到,std::enable_if可以实现强大的重载机制,因为通常必须是参数不同才能重载,如果只有返回值不同是不能重载的,而在上面的例子中,返回类型相同的函数都可以重载。

 

  C 11的type_traits提供了近百个在编译期计算、查询、判断、转换和选择的元函数,为我们编写元程序提供了很大的便利。如果说C 11的type_traits让模版元编程变得简单,那么C 11提供的可变模板参数和tuple则进一步增强了模板元编程。

 

4.可变模板参数

 

  C 11的可变模版参数(variadic templates)是C 11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。关于它的用法和使用技巧读者可以参考笔者在程序员2015年2月A上的文章:泛化之美--C 11可变模版参数的妙用,这里不再赘述,这里将要展示的如何借助可变模板参数实现一些编译期算法,比如获取最大值、判断是否包含了某个类型、根据索引查找类型、获取类型的索引和遍历类型等算法。实现这些算法需要结合type_traits或其它C 11特性,下面来看看这些编译期算法是如何实现的。

 

  编译期从一个整形序列中获取最大值:

 

 

//获取最大的整数

template <size_t arg, size_t... rest>

struct IntegerMax;

 

template <size_t arg>

struct IntegerMax<arg> : std::integral_constant<size_t, arg>

{

};

 

template <size_t arg1, size_t arg2, size_t... rest>

struct IntegerMax<arg1, arg2, rest...> : std::integral_constant<size_t, arg1 >= arg2 ? IntegerMax<arg1, rest...>::value :

    IntegerMax<arg2, rest...>::value >

{

};

 

  这个IntegerMax的实现用到了type_traits中的std::integral_const,它在展开参数包的过程中,不断的比较,直到所有的参数都比较完,最终std::integral_const的value值即为最大值。它的使用很简单:

 

cout << IntegerMax<2, 5, 1, 7, 3>::value << endl; //value为7

 

  我们可以在IntegerMax的基础上轻松的实现获取最大内存对齐值的元函数MaxAlign。

 

  编译期获取最大的align:

 

 

template<typename... Args>

struct MaxAlign : std::integral_constant<int, IntegerMax<std::alignment_of<Args>::value...>::value>{};

cout << MaxAlign<int, short, double, char>::value << endl; //value为8

    编译判断是否包含了某种类型:

template < typename T, typename... List >

struct Contains;

 

template < typename T, typename Head, typename... Rest >

struct Contains<T, Head, Rest...>

    : std::conditional< std::is_same<T, Head>::value, std::true_type, Contains<T, Rest... >> ::type{};

 

template < typename T >

struct Contains<T> : std::false_type{};

用法:cout<<Contains<int, char, double, int, short>::value<<endl; //输出true

 

  这个Contains的实现用到了type_traits的std::conditional、std::is_same、std::true_type和std::false_type,它的实现思路是在展开参数包的过程中不断的比较类型是否相同,如果相同则设置值为true,否则设置为false。

 

        编译期获取类型的索引:

 

 

template < typename T, typename... List >

struct IndexOf;

 

template < typename T, typename Head, typename... Rest >

struct IndexOf<T, Head, Rest...>

{

    enum{ value = IndexOf<T, Rest...>::value 1 };

};

 

template < typename T, typename... Rest >

struct IndexOf<T, T, Rest...>

{

    enum{ value = 0 };

};

 

template < typename T >

struct IndexOf<T>

{

    enum{value = -1};

};

 

  用法:cout<< IndexOf<int, double, short, char, int, float>::value<<endl; //输出3

 

  这个IndexOf的实现比较简单,在展开参数包的过程中看是否匹配到特化的IndexOf<T, T, Rest...>,如果匹配上则终止递归将之前的value累加起来得到目标类型的索引位置,否则将value加1,如果所有的类型中都没有对应的类型则返回-1;

 

  编译期根据索引位置查找类型:

 

 

template<int index, typename... Types>

struct At;

 

template<int index, typename First, typename... Types>

struct At<index, First, Types...>

{

    using type = typename At<index - 1, Types...>::type;

};

 

template<typename T, typename... Types>

struct At<0, T, Types...>

{

    using type = T;

};

    用法:

using T = At<1, int, double, char>::type;

    cout << typeid(T).name() << endl; //输出double

 

  At的实现比较简单,只要在展开参数包的过程中,不断的将索引递减至0时为止即可获取对应索引位置的类型。接下来看看如何在编译期遍历类型。

 

 

template<typename T>

void printarg()

{

    cout << typeid(T).name() << endl;

}

 

template<typename... Args>

void for_each() 

{

    std::initializer_list<int>{(printarg<Args>(), 0)...};

}

用法:for_each<int,double>();//将输出int double

 

  这里for_each的实现是通过初始化列表和逗号表达式来遍历可变模板参数的。

 

  可以看到,借助可变模板参数和type_traits以及模板偏特化和递归等方式我们可以实现一些有用的编译期算法,这些算法为我们编写应用层级别的代码奠定了基础,后面模板元编程的具体应用中将会用到这些元函数。

 

  C 11提供的tuple让我们编写模版元程序变得更灵活了,在一定程度上增强了C 的泛型编程能力,下面来看看tuple如何应用于元程序中的。

 

5.tuple与模版元

 

  C 11的tuple本身就是一个可变模板参数组成的元函数,它的原型如下:

 

template<class...Types>

class tuple;

 

  tuple在模版元编程中的一个应用场景是将可变模板参数保存起来,因为可变模板参数不能直接作为变量保存起来,需要借助tuple保存起来,保存之后再在需要的时候通过一些手段将tuple又转换为可变模板参数,这个过程有点类似于化学中的“氧化还原反应”。看看下面的例子中,可变模板参数和tuple是如何相互转换的:

 

 

//定义整形序列

template<int...>

struct IndexSeq{};

 

//生成整形序列

template<int N, int... Indexes>

struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...>{};

 

template<int... indexes>

struct MakeIndexes<0, indexes...>{

    typedef IndexSeq<indexes...> type;

};

 

template<typename... Args>

void printargs(Args... args){

    //先将可变模板参数保存到tuple中

    print_helper(typename MakeIndexes<sizeof... (Args)>::type(), std::make_tuple(args...));

}

 

template<int... Indexes, typename... Args>

void print_helper(IndexSeq<Indexes...>, std::tuple<Args...>&& tup){

    //再将tuple转换为可变模板参数,将参数还原回来,再调用print

    print(std::get<Indexes>(tup)...); 

}

template<typename T>

void print(T t)

{

    cout << t << endl;

}

 

template<typename T, typename... Args>

void print(T t, Args... args)

{

    print(t);

    print(args...);

}

 

  用法:printargs(1, 2.5, “test”); //将输出1 2.5 test

 

  上面的例子print实际上是输出可变模板参数的内容,具体做法是先将可变模板参数保存到tuple中,然后再通过元函数MakeIndexes生成一个整形序列,这个整形序列就是IndexSeq<0,1,2>,整形序列代表了tuple中元素的索引,生成整形序列之后再调用print_helper,在print_helper中展开这个整形序列,展开的过程中根据具体的索引从tuple中获取对应的元素,最终将从tuple中取出来的元素组成一个可变模板参数,从而实现了tuple“还原”为可变模板参数,最终调用print打印可变模板参数。

 

  tuple在模板元编程中的另外一个应用场景是用来实现一些编译期算法,比如常见的遍历、查找和合并等算法,实现的思路和可变模板参数实现的编译期算法类似,关于tuple相关的算法,读者可以参考笔者在github上的代码:

 

  下面来看看模版元的具体应用。

 

6.模版元的应用

 

  我们将展示如何通过模版元来实现function_traits和Vairant类型。

 

  function_traits用来获取函数语义的可调用对象的一些属性,比如函数类型、返回类型、函数指针类型和参数类型等。下面来看看如何实现function_traits。

 

 

template<typename T>

struct function_traits;

 

//普通函数

template<typename Ret, typename... Args>

struct function_traits<Ret(Args...)>

{

public:

    enum { arity = sizeof...(Args) };

    typedef Ret function_type(Args...);

    typedef Ret return_type;

    using stl_function_type = std::function<function_type>;

    typedef Ret(*pointer)(Args...);

 

    template<size_t I>

    struct args

    {

        static_assert(I < arity, "index is out of range, index must less than sizeof Args");

        using type = typename std::tuple_element<I, std::tuple<Args...>>::type;

    };

};

 

//函数指针

template<typename Ret, typename... Args>

struct function_traits<Ret(*)(Args...)> : function_traits<Ret(Args...)>{};

 

//std::function

template <typename Ret, typename... Args>

struct function_traits<std::function<Ret(Args...)>> : function_traits<Ret(Args...)>{};

 

//member function

#define FUNCTION_TRAITS(...)

    template <typename ReturnType, typename ClassType, typename... Args>

    struct function_traits<ReturnType(ClassType::*)(Args...) __VA_ARGS__> : function_traits<ReturnType(Args...)>{};

 

FUNCTION_TRAITS()

FUNCTION_TRAITS(const)

FUNCTION_TRAITS(volatile)

FUNCTION_TRAITS(const volatile)

 

//函数对象

template<typename Callable>

struct function_traits : function_traits<decltype(&Callable::operator())>{};

 

  由于可调用对象可能是普通的函数、函数指针、lambda、std::function和成员函数,所以我们需要针对这些类型分别做偏特化。其中,成员函数的偏特化稍微复杂一点,因为涉及到cv符的处理,这里通过定义一个宏来消除重复的模板类定义。参数类型的获取我们是借助于tuple,将参数转换为tuple类型,然后根据索引来获取对应类型。它的用法比较简单:

 

 

template<typename T>

void PrintType()

{

    cout << typeid(T).name() << endl;

}

int main()

{

    std::function<int(int)> f = [](int a){return a; };

    PrintType<function_traits<std::function<int(int)>>::function_type>(); //将输出int __cdecl(int)

    PrintType<function_traits<std::function<int(int)>>::args<0>::type>();//将输出int

    PrintType<function_traits<decltype(f)>::function_type>();//将输出int __cdecl(int)

}

 

  有了这个function_traits和前面实现的一些元函数,我们就能方便的实现一个“万能类型”—Variant,Variant实际上一个泛化的类型,这个Variant和boost.variant的用法类似。boost.variant的基本用法如下:

 

typedef variant<int,char, double> vt;

vt v = 1;

v = 'a';

v = 12.32;

  这个variant可以接受已经定义的那些类型,看起来有点类似于c#和java中的object类型,实际上variant是擦除了类型,要获取它的实际类型的时候就稍显麻烦,需要通过boost.visitor来访问:

 

 

  通过C 11模版元实现的Variant将改进值的获取,将获取实际值的方式改为内置的,即通过下面的方式来访问:

 

typedef Variant<int, double, string, int> cv;

cv v = 10;

v.Visit([&](double i){cout << i << endl; }, [](short i){cout << i << endl; }, [=](int i){cout << i << endl; },[](const string& i){cout << i << endl; });//结果将输出10

  这种方式更方便直观。Variant的实现需要借助前文中实现的一些元函数MaxInteger、MaxAlign、Contains和At等等。下面来看看Variant实现的关键代码,完整的代码请读者参考笔者在github上的代码

 

 View Code

  实现Variant首先需要定义一个足够大的缓冲区用来存放不同的类型的值,这个缓类型冲区实际上就是用来擦除类型,不同的类型都通过placement new在这个缓冲区上创建对象,因为类型长度不同,所以需要考虑内存对齐,C 11刚好提供了内存对齐的缓冲区aligned_storage:

 

template< std::size_t Len, std::size_t Align = /*default-alignment*/ >

struct aligned_storage;

  它的第一个参数是缓冲区的长度,第二个参数是缓冲区内存对齐的大小,由于Varaint可以接受多种类型,所以我们需要获取最大的类型长度,保证缓冲区足够大,然后还要获取最大的内存对齐大小,这里我们通过前面实现的MaxInteger和MaxAlign就可以了,Varaint中内存对齐的缓冲区定义如下:

 

enum

{

    data_size = IntegerMax<sizeof(Types)...>::value,

    align_size = MaxAlign<Types...>::value

};

using data_t = typename std::aligned_storage<data_size, align_size>::type; //内存对齐的缓冲区类型

  其次,我们还要实现对缓冲区的构造、拷贝、析构和移动,因为Variant重新赋值的时候需要将缓冲区中原来的类型析构掉,拷贝构造和移动构造时则需要拷贝和移动。这里以析构为例,我们需要根据当前的type_index来遍历Variant的所有类型,找到对应的类型然后调用该类型的析构函数。

 

 

void Destroy(const type_index& index, void * buf)

    {

        std::initializer_list<int>{(Destroy0<Types>(index, buf), 0)...};

    }

 

    template<typename T>

    void Destroy0(const type_index& id, void* data)

    {

        if (id == type_index(typeid(T)))

            reinterpret_cast<T*>(data)->~T();

    }

 

  这里,我们通过初始化列表和逗号表达式来展开可变模板参数,在展开的过程中查找对应的类型,如果找到了则析构。在Variant构造时还需要注意一个细节是,Variant不能接受没有预先定义的类型,所以在构造Variant时,需要限定类型必须在预定义的类型范围当中,这里通过type_traits的enable_if来限定模板参数的类型。

 

 

template <class T,

    class = typename std::enable_if<Contains<typename std::remove_reference<T>::type, Types...>::value>::type> Variant(T&& value) : m_typeIndex(typeid(void)){

            Destroy(m_typeIndex, &m_data);

            typedef typename std::remove_reference<T>::type U;

            new(&m_data) U(std::forward<T>(value));

            m_typeIndex = type_index(typeid(U));

    }

 

  这里enbale_if的条件就是前面实现的元函数Contains的值,当没有在预定义的类型中找到对应的类型时,即Contains返回false时,编译期会报一个编译错误。

 

  最后还需要实现内置的Vistit功能,Visit的实现需要先通过定义一系列的访问函数,然后再遍历这些函数,遍历过程中,判断函数的第一个参数类型的type_index是否与当前的type_index相同,如果相同则获取当前类型的值。

 

 

template<typename F>

    void Visit(F&& f){

        using T = typename Function_Traits<F>::template arg<0>::type;

        if (Is<T>())

            f(Get<T>());

    }

 

    template<typename F, typename... Rest>

    void Visit(F&& f, Rest&&... rest){

        using T = typename Function_Traits<F>::template arg<0>::type;

        if (Is<T>())

            Visit(std::forward<F>(f));

        else

            Visit(std::forward<Rest>(rest)...);

    }

 

  Visit功能的实现利用了可变模板参数和function_traits,通过可变模板参数来遍历一系列的访问函数,遍历过程中,通过function_traits来获取第一个参数的类型,和Variant当前的type_index相同的则取值。为什么要获取访问函数第一个参数的类型呢?因为Variant的值是唯一的,只有一个值,所以获取的访问函数的第一个参数的类型就是Variant中存储的对象的实际类型。

 

7总结

 

  C 11中的一些特性比如type_traits、可变模板参数和tuple让模版元编程变得更简单也更强大,模版元编程虽然功能强大,但也比较复杂,要用好模版元,需要我们转变思维方式,在掌握基本的理论的基础上,再认真揣摩模版元的一些常用技巧,这些技巧是有规律可循的,基本上都是通过重定义、递归和偏特化等手法来实现的,当我们对这些基本技巧很熟悉的时候再结合不断地实践,相信对模版元编程就能做到“游刃有余”了。

 

 

1.概述 模版元编程(template metaprogram)是C 中最复杂也是威力最强大的编程范式,它是一种可以创建和操纵程序的程序。模...

自动类型推导 auto

在这一节中,原文主要介绍了两个关键字 auto 和 deltype,示例如下:

auto x=0; //x has type int because 0 is int

auto c='a'; //char

auto d=0.5; //double

auto national_debt=14400000000000LL;//long long

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">auto 最大的好处就是让代码简洁,尤其是那些模板类的声明,比如:STL中的容器的迭代子类型。</span>

vector<int>::const_iterator ci = vi.begin();

可以变成:

auto ci = vi.begin();

模板这个特性让C 的代码变得很难读,不信你可以看看STL的源码,那是一个乱啊。使用auto必需一个初始化值,编译器可以通过这个初始化值推导出类型。因为auto是来简化模板类引入的代码难读的问题,如上面的示例,iteration这种类型就最适合用auto的,但是,我们不应该把其滥用。

比如下面的代码的可读性就降低了。因为,我不知道ProcessData返回什么?int? bool? 还是对象?或是别的什么?这让你后面的程序不知道怎么做。

auto obj = ProcessData(someVariables);

但是下面的程序就没有问题,因为pObject的型别在后面的new中有了。

auto pObject = new SomeType<othertype>::SomeOtherType();

统一初始化和初始化列表

C 11 introduced the concept of uniform initialization, which means that for any initialization, you can use one common syntax. This syntax uses braces, so the following is possible now:

int values[] { 1, 2, 3 };

std::vectorv { 2, 3, 5, 7, 11, 13, 17 };

std::vectorcities {"Berlin", "New York", "London", "Braunschweig", "Cairo", "Cologne"};

std::complexc{4.0,3.0}; // equivalent to c(4.0,3.0)

使用初始化列表初始化时,不提供初始值时默认值初始化(0或nullptr),其他初始化方式若为提供初始值则值是未定义的。

int i; // i has undefined value

int j{}; // j is initialized by 0

int* p; // p has undefined value

int* q{}; // q is initialized by nullptr

使用初始化列表初始化时,若提供的值会将会损失精度时,编译器报错。

int x1(5.3); // OK, but OUCH: x1 becomes 5

int x2 = 5.3; // OK, but OUCH: x2 becomes 5

int x3{5.0}; // ERROR: narrowing

int x4 = {5.3}; // ERROR: narrowing

char c1{7}; // OK: even though 7 is an int, this is not narrowing

char c2{99999}; // ERROR: narrowing (if 99999 doesn’t fit into a char)

std::vector<int>v1 { 1, 2, 4, 5 }; // OK

std::vector<int>v2 { 1, 2.3, 4, 5.6 }; // ERROR: narrowing doubles to ints

std::initializer_list<>使得函数支持传入多值(通过初始值列表)

void print (std::initializer_list<int> vals){

           for (auto p=vals.begin(); p!=vals.end(); p) { 

                    std::cout << *p << "n";

                    }

}

print ({12,3,5,7,11,13,17}); // pass a list of values to print()

新的算法

定义了一些新的算法: all_of(), any_of() 和 none_of()。

#include <algorithm>

//C 11 code

//are all of the elements positive?

all_of(first, first n, ispositive; //false

//is there at least one positive element?

any_of(first, first n, ispositive;//true

// are none of the elements positive?

none_of(first, first n, ispositive; //false

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">使用新的copy_n()算法,你可以很方便地拷贝数组。</span>

#include <algorithm>

int source[5]={0,12,34,50,80};

int target[5];

//copy 5 elements from source to target

copy_n(source,5,target);

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">使用 </span><code style="font-size: 13px; line-height: 19px; white-space: normal;">iota()</code><span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;"> 可以用来创建递增的数列。如下例所示:</span>

include <numeric>

int a[5]={0};

char c[3]={0};

iota(a, a 5, 10); //changes a to {10,11,12,13,14}

iota(c, c 3, 'a'); //{'a','b','c'}

总之,看下来,C 11 还是很学院派,很多实用的东西还是没有,比如: XML,sockets,reflection,当然还有垃圾回收。看来要等到C 20了。呵呵。不过C 11在性能上还是很快。原文还引用Stroustrup 的观点:C 11 是一门新的语言——一个更好的 C 。

如果把所有的改变都列出来,你会发现真多啊。我估计C Primer那本书的厚度要增加至少30%以上。C 的门槛会不会越来越高了呢?我不知道,但我个人觉得这门语言的确是变得越来越令人望而却步了,但是其重要性还是不言而喻的!(想起了某人和我说的一句话——学技术真的是太累了,还是搞方法论好混些?)

自动类型推断

auto关键字,由auto定义的变量必须初始化。

新型智能指针

C 98 的知能指针是 auto_ptr, 在C 11中被废弃了。C 11 引入了两个指针类: shared_ptr 和 unique_ptr。 shared_ptr只是单纯的引用计数指针,unique_ptr 是用来取代auto_ptr。 unique_ptr 提供 auto_ptr 大部份特性,唯一的例外是 auto_ptr 的不安全、隐性的左值搬移。不像 auto_ptr,unique_ptr 可以存放在 C 0x 提出的那些能察觉搬移动作的容器之中。

为什么要这么干?大家可以看看《More Effective C 》中对 auto_ptr的讨论。

空指针

nullptr 空指针的字面值常量,它的类型是std::nullptr_t(定义位于cstddef)

Lambda 表达式

Lambda表达式来源于函数式编程,说白就了就是在使用的地方定义函数,有的语言叫“闭包”,如果 lambda 函数没有传回值,其回返类型可被完全忽略。 定义在与 lambda 函数相同作用域的变量参考也可以被使用。这种的变量集合一般被称作 closure。我在这里就不再讲这个事了。表达式的简单语法如下,

[capture](parameters)->return_type {body}

原文的作者给出了下面的例子:

int main()

{

char s[]="Hello World!";

int Uppercase = 0; //modified by the lambda

for_each(s, s sizeof, [&Uppercase] {

if (isupper

Uppercase ;

});

cout << Uppercase << " uppercase letters in: " << s <<endl;

}

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">在传统的STL中for_each() 这个玩意最后那个参数需要一个“函数对象”,所谓函数对象,其实是一个class,这个class重载了operator(),于是这个对象可以像函数的式样的使用。实现一个函数对象并不容易,需要使用template,比如下面这个例子就是函数对象的简单例子(实际的实现远比这个复杂):</span>

template <class T>

class less

{

public:

bool operator()(const T&l, const T&r)const

{

return l < r;

}

};

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">所以,</span><strong style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">C 引入Lambda的最主要原因就是1)可以定义匿名函数,2)编译器会把其转成函数对象</strong><span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">。相信你会和我一样,会疑问为什么以前STL中的ptr_fun()这个函数对象不能用?就是把一个自然函数转成函数对象的)。原因是,ptr_fun() 的局限是其接收的自然函数只能有1或2个参数。</span>

那么,除了方便外,为什么一定要使用Lambda呢?它比传统的函数或是函数对象有什么好处呢?我个人所理解的是,这种函数之年以叫“闭包”,就是因为其限制了别人的访问,更私有。也可以认为他是一次性的方法。Lambda表达式应该是简洁的,极私有的,为了更易的代码和更方便的编程。

新的基本数据类型

char16_t, char32_t, long long, unsigned long long ,std::nullptr_t

我在这里仅对文中提到的这些变化“追问为什么要引入这些变化”的一个探讨,只有知道为了什么,用在什么地方,我们才能真正学到这个知识。而以此你可以更深入地了解这些变化。所以,本文不是翻译。因为写得有些仓促,所以难免有问题,还请大家指正。

基于范围的循环表达式

for ( decl : coll ) {

        statement

}

coll必须是支持迭代器的容器,或者是数组,或者是值列表类型

线程库

这们就不多说了,以前的STL饱受线程安全的批评。现在好 了。C 11 支持线程类了。这将涉及两个部分:第一、设计一个可以使多个线程在一个进程中共存的内存模型;第二、为线程之间的交互提供支持。第二部分将由程序库提供支持。大家可以看看promises and futures,其用于对象的同步。 async() 函数模板用于发起并发任务,而 thread_local 为线程内的数据指定存储类型。更多的东西,可以查看 Anthony Williams的 Simpler Multithreading in C 0x.

main函数的定义

1,

int main{}

2,

int main (int argc, char* argv[]){}

不一定要return 语句,隐式返回0,表示成功执行;返回非0则表示出错;

不使用return来结束程序可使用exit(), quick_exit(), terminate();

统一的初始化语法

C/C 的初始化的方法比较,C 11 用大括号统一了这些初始化的方法。

比如:POD的类型。

int arr[4]={0,1,2,3};

struct tm today={0};

关于POD相说两句,所谓POD就是Plain Old Data,当class/struct是极简的、属于标准布局(standard-layout),以及他的所有非静态(non-static)成员都是POD时,会被视为POD。如:

struct A { int m; }; // POD

struct B { ~B(); int m; }; // non-POD, compiler generated default ctor

struct C { C {}; ~C(); int m; }; // non-POD, default-initialising m

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">POD的初始化有点怪,比如上例,new A; 和new A(); 是不一样的,对于其内部的m,前者没有被初始化,后者被初始化了(不同 的编译器行为不一样,VC 和GCC不一样)。而非POD的初始化,则都会被初始化。</span>

从这点可以看出,C/C 的初始化问题很奇怪,所以,在C 2011版中就做了统一。原文作者给出了如下的示例:

C c {0,0}; //C 11 only. 相当于: C c;

int* a = new int[3] { 1, 2, 0 }; /C 11 only

class X {

int a[4];

public:

X() : a{1,2,3,4} {} //C 11, member array initializer

};

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">容器的初始化:</span>

// C 11 container initializer

vector<string> vs={ "first", "second", "third"};

map singers =

{ {"Lady Gaga", " 1 555-7890"},

{"Beyonce Knowles", " 1 555-0987"}};

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">还支持像Java一样的成员初始化:</span>

class C

{

int a=7; //C 11 only

public:

C();

};

<strong style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;"> </strong>

<strong style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">Delete 和 Default 函数</strong>

我们知道C 的编译器在你没有定义某些成员函数的时候会给你的类自动生成这些函数,比如,构造函数,拷贝构造,析构函数,赋值函数。有些时候,我们不想要这些函数,比如,构造函数,因为我们想做实现单例模式。传统的做法是将其声明成private类型。

在新的C 中引入了两个指示符,delete意为告诉编译器不自动产生这个函数,default告诉编译器产生一个默认的。原文给出了下面两个例子:

struct A

{

A()=default; //C 11

virtual ~A()=default; //C 11

};

再如delete

struct NoCopy

{

NoCopy & operator =( const NoCopy & ) = delete;

NoCopy ( const NoCopy & ) = delete;

};

NoCopy a;

NoCopy b; //compilation error, copy ctor is deleted

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">这里,我想说一下,为什么我们需要default?我什么都不写不就是default吗?不全然是,比如构造函数,因为只要你定义了一个构造函数,编译器就不会给你生成一个默认的了。所以,为了要让默认的和自定义的共存,才引入这个参数,如下例所示:</span>

struct SomeType

{

SomeType() = default; // 使用编译器生成的默认构造函数

SomeType(OtherType value);

};

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">关于delete还有两个有用的地方是</span>

1)让你的对象只能生成在栈内存上:

struct NonNewable {

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

};

2)阻止函数的其形参的类型调用:(若尝试以 double 的形参调用 f(),将会引发编译期错误, 编译器不会自动将 double 形参转型为 int 再调用f(),如果传入的参数是double,则会出现编译错误)

void f;

void f = delete;

模板特性

Variadic Templates

可变模板参数个数,例如处理不同类型的参数

void print (){}

template<typename T, typename... Types>

 void print (const T& firstArg, const Types&... args)

{

           std::cout << firstArg << std::endl; // print first argument

            print(args...); // call print() for remaining arguments

}

Alias Templates (Template Typedef)

template<typename T>

using Vec = std::vector>; // standard vector using own allocator

Vec coll;//即std::vector<int, MyAlloc<int>> coll;

委托构造

在以前的C 中,构造函数之间不能互相调用,所以,我们在写这些相似的构造函数里,我们会把相同的代码放到一个私有的成员函数中。

class SomeType {

private:

int number;

string name;

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

public:

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

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

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

};

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">但是,为了方便并不足让“委托构造”这个事出现,最主要的问题是,基类的构造不能直接成为派生类的构造,就算是基类的构造函数够了,派生类还要自己写自己的构造函数:</span>

class BaseClass

{

public:

BaseClass(int iValue);

};

class DerivedClass : public BaseClass

{

public:

using BaseClass::BaseClass;

};

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

其他新特性

Nontype Template Parameters

bitset<32> flags32; // bitset with 32 bits

bitset<50> flags50; // bitset with 50 bits

Default Template Parameters

template<typename T, typename container = vector<T>>

class MyClass;

MyClass<int> x1;//即MyClass<int, vector<int>>

Keyword typename

用来说明其修饰的东西是类型

Member Templates

类的成员函数可以为模板

Nested Class Templates

嵌套类也可以为类模板

显式初始化基本数据类型

使用不带参数的显式构造初始化基本数据类型时,为0初始化(所有值都为0)

int i1; // undefined value

int i2 = int(); // initialized with zero

int i3{}; // initialized with zero (since C 11)

右值引用和move语义

在老版的C 中,临时性变量(称为右值”R-values”,位于赋值操作符之右)经常用作交换两个变量。比如下面的示例中的tmp变量。示例中的那个函数需要传递两个string的引用,但是在交换的过程中产生了对象的构造,内存的分配还有对象的拷贝构造等等动作,成本比较高。

void naiveswap(string &a, string &b)

{

string temp = a;

a=b;

b=temp;

}

C 11增加一个新的引用(reference)类型称作右值引用(R-value reference),标记为typename &&。他们能够以non-const值的方式传入,允许对象去改动他们。这项修正允许特定对象创造出move语义。

举例而言,上面那个例子中,string类中保存了一个动态内存分存的char*指针,如果一个string对象发生拷贝构造,string类里的char*内存只能通过创建一个新的临时对象,并把函数内的对象的内存copy到这个新的对象中,然后销毁临时对象及其内存。这是原来C 性能上重点被批评的事

能过右值引用,string的构造函数需要改成“move构造函数”,如下所示。这样一来,使得对某个stirng的右值引用可以单纯地从右值复制其内部C-style的指针到新的string,然后留下空的右值。这个操作不需要内存数组的复制,而且空的暂时对象的析构也不会释放内存。其更有效率。

class string

{

string (string&&); //move constructor

string&& operator=(string&&); //move assignment operator

};

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">The C 11 STL中广泛地使用了右值引用和move语议。因此,很多算法和容器的性能都被优化了。</span>

lambda表达式

基本语法:

[ capture list ] { function body } 或

[ capture list ]   ( paramters list )   mutable   throwSpec   -> retType { function body }

mutable、  throwSpec、  -> retType 可选

调用:直接调用、生成lambda变量间接调用

[ ] { std::cout << "hello lambda" << std::endl; } (); 或

auto func = [ ] (const std::string &s){ std::cout << "hello lambda"  <<s << std::endl; };

func("good");

使用尾置返回:

未指明返回值类型时,若只有一个return语句,根据返回值自行推断;若有其他语句则返回void。

捕获规则:全局变量不用捕获即可使用

[ ]:不捕获任何变量

[=]:值捕获本作用域外变量

[&]:引用捕获作用域外变量

[x, &y]:显式值捕获x,引用捕获y

[=, &y]:值捕获除y的所有变量,y为引用捕获

[&, x]:引用捕获除x的所有变量,x为值捕获

值捕获变量在定义lambda时就已经确定,在内部不可更改其值,但使用mutable说明符可以使之改变。

lambda的类型

lambda的类型是匿名函数,每个不同的lambda的类型是不一样的。

#include<functional>

例如表达式

[ ] (int a, int b) -> int {  return a b; }

的类型是std::function<int(int ,int)>,用作返回值类型,可以返回一个lambda对象

std::function<int(int, int)>returnLambda () {

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

}

auto f = returnLambda();

cout <<f(1,2)<<endl;

自动化推导 decltype

关于 decltype 是一个操作符,其可以评估括号内表达式的类型,其规则如下:

如果表达式e是一个变量,那么就是这个变量的类型。

如果表达式e是一个函数,那么就是这个函数返回值的类型。

如果不符合1和2,如果e是左值,类型为T,那么decltype是T&;如果是右值,则是T。

原文给出的示例如下,我们可以看到,这个让的确我们的定义变量省了很多事。

const vector<int> vi;

typedef decltype (vi.begin CIT;

CIT another_const_iterator;

还有一个适合的用法是用来typedef函数指针,也会省很多事。比如:

decltype(&myfunc) pfunc = 0;

typedef decltype(&A::func1) type;

auto 和 decltype 的差别和关系

Wikipedia 上是这么说的(关于decltype的规则见上)

#include <vector>

int main()

{

const std::vector<int> v;

auto a = v[0]; // a 的类型是 int

decltype b = 1; // b 的类型是 const int&, 因为函数的返回类型是

// std::vector<int>::operator[](size_type) const

auto c = 0; // c 的类型是 int

auto d = c; // d 的类型是 int

decltype e; // e 的类型是 int, 因为 c 的类型是int

decltype f = c; // f 的类型是 int&, 因为 是左值

decltype g; // g 的类型是 int, 因为 0 是右值

}

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">如果auto 和 decltype 在一起使用会是什么样子?能看下面的示例,下面这个示例也是引入decltype的一个原因——让C 有能力写一个 “ </span><a style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;" href=" function</a><span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;"> 模板”,</span>

template< typename LHS, typename RHS>

auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype

{return lhs rhs;}

<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;">这个函数模板看起来相当费解,其用到了auto 和 decltype 来扩展了已有的模板技术的不足。怎么个不足呢?在上例中,我不知道AddingFunc会接收什么样类型的对象,这两个对象的 操作符返回的类型也不知道,老的模板函数无法定义AddingFunc返回值和这两个对象相加后的返回值匹配,所以,你可以使用上述的这种定义。</span>

关键字decltype

编译器推断表达式的类型:用于声明返回值类型,元编程或传递lambda的类型

std::map<std::string, sloat> coll;

decltype(coll)::value_type elem;

新的函数声明语法

template<typename T1, typename T2>

auto add(T1 x, T2 y) -> decltype(x y);//尾置返回,确定返回类型必须预先知道x,y的类型,但返回类型推断放在函数名前面无效(x,y类型此时未知)

C 11 STL 标准库

C STL库在2003年经历了很大的整容手术 Library Technical Report 1 。 TR1 中出现了很多新的容器类 (unordered_set, unordered_map, unordered_multiset, 和 unordered_multimap) 以及一些新的库支持诸如:正则表达式, tuples,函数对象包装,等等。 C 11 批准了 TR1 成为正式的C 标准,还有一些TR1 后新加的一些库,从而成为了新的C 11 STL标准库。这个库主要包含下面的功能:

关键字noexcept

声明一个函数不能或不准备抛出异常。

void foo() noexcept;

若在foo函数内部没有处理发生的异常,程序会终止,调用std::terminate(),它默认调用std::abort();

nullptr

C/C 的NULL宏是个被有很多潜在BUG的宏。因为有的库把其定义成整数0,有的定义成 0。在C的时代还好。但是在C 的时代,这就会引发很多问题。你可以上网看看。这是为什么需要 nullptr 的原因。 nullptr 是强类型的。

void f; //#1

void f;//#2

//C 03

f; //二义性

//C 11

f //无二义性,调用f

所以在新版中请以 nullptr 初始化指针。

原始字符串字面值、被编码的字符串字面值

Raw String Literals

此类型的字符串可包含特殊字符,语法R"delim(...)delim",delim是16个基本字符组成的分隔符除了反斜杠,空格,圆括号;

例如:R"(\n)"对应的普通字符串是"\n"

常用来定义正则表达式。

Encoded String Literals

使用编码前缀,定义特殊编码的字符串常量:

u8:utf-8,const char

u:char16_t

U:char32_t

L:wchar_t

作用域枚举(强枚举,枚举类)

enum class Salutation : char { mr, ms, co, none };

1,不可隐式的转换到int或从int转换

2,使用作用域运算符引用Salutation::mr;

3,可以显式定义类型(:char),否则默认是:int

4,可前向声明枚举类型

关键字constexpr

使得表达式在编译期间判断是否是常量,或者修饰返回值返回一个常量。

constexpr int square (int x){

               return x * x;

}

float a[square(9)]; // OK since C 11: a has 81 elements

本文由9159.com发布于编程,转载请注明出处:因为模版元程序的执行完全是在编译期,由auto定

关键词: 9159.com