第四章 新手易学,老兵易用
4.1 右尖括号>的改进
在 C++98 中,有一条需要程序员规避的规则:如果在实例化模板的时候出现了连续的两个右尖括号 >,那么它们之间需要一个空格来进行分隔,以避免发生编译时的错误。C++98 会将>>优先解析为右移。C++11 中,这种限制被取消了。事实上,C++11标准要求编译器智能地去判断在哪些情况下 >> 不是右移符号。
4.2 auto类型推导
4.2.1 静态类型、动态类型与类型推导
在 C++11 中,标准委员会决定赋予 auto 全新的含义,即auto 不再是一个存储类型指示符 ( storage-class-specifier,如 static、extern、thread local等都是存储类型指示符 ),而是作为一个新的类型指示符 ( type-specifier,如 int、float 等都是类型指示符)来指示编译器auto 声明变量的类型必须由编译器在编译时期推导而得。auto声明的变量必须被初始化,以使编译器能够从其初始化表达式中推导出其类型。
4.2.2 auto的优势
auto推导的优势:1.在拥有初始化表达式的复杂类型变量声明时简化代码。2.免除在一些类型声明时的麻烦,或者避免一些在类型声明时的错误。特别是在一些隐式或者用户自定义类型转换的情况。3.自适应性能够在一定程度上支持泛型的编程。这里举了一个例子:
Max2因为可以保存运算结果,提高了性能。而c++98标准中,因为无法获得a和b类型,因此只能实现Max1宏。
4.2.3 auto的使用细则
首先,c++11中,auto可以与指针和引用结合起来使用,效果符合c/c++程序员的想象。
其次,auto与volatile和const之间也存在着一些相互的联系。声明为auto的变量并不能从其初始化表达式中带走cv限制符。
此外,跟其他的变量指示符一样,同一个赋值语句中,auto可以用来声明多个变量的类型,不过这些变量的类型必须相同。
4种不能推导的情况:
1)对于函数fun来说,auto不能是其形参类型。
2)对于结构体来说,非静态成员变量的类型不能是auto的。
3)声明auto数组。auto z[3]这样的数组会被编译器禁止。
4)在实例化模板的时候使用auto作为模板参数。
为了避免和c++98中auto的含义发生混淆,c++11只保留auto作为类型指示符的用法
4.3 decltype
4.3.1 typeid与decltype
c完全不支持动态类型,c++98部分支持动态类型,也就是运行时类型识别(RTTI)。
RTTI机制为每个类型产生一个type_info类型的数据,程序员可以在程序中使用typeid随时查询一个变量的类型,typeid就会返回变量相应的type_info数据。而type_info的name成员函数可以返回类型的名字。而c++11中又曾佳乐hash_code这个成员函数,返回该类型唯一的哈希值,以供程序员对变量的类型随时进行比较。
除了typeid外,RTTI还包括c++中的dynamic_cast等特性。
事实上,在c++的发展中,类型推导是随着模板和泛型编程的广泛使用而引入的。
c++ 11对类型推导手段进行了细致的考量,最终标准化为了auto以及decltype。
decltype的类型推导总是以一个普通的表达式为参数,返回该表达式的类型。作为一个类型指示符,decltype可以将获得的类型来定义另外一个变量。decltype类型推导也是在编译时进行的。
4.3.2 decltype的应用
c++11中,比较典型的就是decltype与typedef/using的合用。
decltype在某些场景下,可以极大地增加代码的可读性。
在c++中,有时会遇到匿名类型,使用decltype可以重用匿名类型。
有了decltype可以适当扩大模板泛型的能力。
decltype在标准库中也有一些应用,例如基于decltype的模板类result_of,其作用是推导函数的返回类型。
4.3.3 decltype推导四规则
1.如果e是一个没有带括号的标记符表达式或类成员访问表达式,那么decltype(e)就是e所命名的实体的类型。此外,如果e是一个被重载的函数,则会导致编译时错误。
2.否则,假设e的类型是T,如果e是一个将亡值xvalue,那么decltype(e)为T&&。
3.否则,假设e的类型是T,如果e是一个左值,则decltype(e)为T&。
4.否则,假设e的类型是T,则decltype(e)为T。
标记符表达式 (id-expression ),基本上,所有除去关键字、字面量等编译器需要使用的标记之外的程序员自定义的标记 (token ) 都可以是标记符 (identifier )。而单个标记符对应的表达式就是标记符表达式。
4.3.4 cv限制符的继承与冗余的符号
与auto类型推导时不能带走cv限制符不同,decltype能够带走表达式的cv限制符。不过,如果对象的定义中有const或volatile限制符,使用decltype进行推导时,其成员不会继承const或volatile限制符。
与auto相同,decltype从表达式推导出类型后,进行类型定义时,也会允许一些冗余的符号。比如cv限制符及引用符号&,通常如果推导出的类型已经有了这些属性,冗余的符号则会被忽略。
4.4 追踪返回类型
4.4.1 追踪返回类型的引入
c++98中,如果一个函数模板的返回类型依赖于实际的入口参数类型,那么该返回类型在模板实例化之前可能都无法确定,这样的话在定义函数模板时就会遇到麻烦。
最直观的解决方式时对返回类型进行类型推导。
编译器在推导decltype(t1+t2)时,表达式t1和t2都未声明,为了解决这个问题,c++11引入新语法--追踪返回类型,来声明和定义这样的函数。
auto占位符和->return_type构成追踪返回类型函数的两个基本元素。
4.4.2 使用追踪返回类型的函数
4.5 基于范围的for循环
对于一个有范围的集合,说明循环的范围是多余的。可以使用基于范围的for循环来遍历:
如果不需要修改迭代变量的值,也可以不使用引用:
使用auto可以更简练:
使用for循环迭代要求范围是确定的,数组大小不能确定,是不能使用基于范围的for循环,例如:
例子里,作为参数传过来的数组a的范围不能确定。
基于范围的循环使用标准库容器时,如果使用auto来声明迭代的对象时,这个对象不是迭代器对象,而是解引用后的对象。