C++11 新特性——类型推导

 
 

写在前面

  C++ 是有四个相关语言组成的联邦——C、object-C、template C++、STL。如果需要将这些东西全部杂糅到一起,总会出现一些抵触的情况,C++ 的编码也会变得更复杂。新标准增加了许多新特性,使得 C++ 更加易用。这些新特性是紧密相连,互为基础的。
  在C++11中,规范提供了多种类型推导的机制,使得我们写出来的代码更精简、更灵活。
  这个新的类型推导机制,主要由两个新的关键字表示:auto 和 decltype。虽然它们之间有些重叠,但使用场景是不同的。auto 只能用来声明变量,而 decltype 则更为通用。

 

auto 类型推导

  在 C++ 中,每个变量使用前必须定义几乎是天经地义的事情。像 Python、JavaScript 等动态语言不需要声明,几乎拿来就用。静态类型和动态类型的主要区别在于对变量进行类型检查的时间点。对于静态类型,类型检查主要发生在编译阶段;对于动态语言,类型检查主要发生在运行阶段。从信息的角度来说,其实编译期就能够得到定义类型的类型信息。如果能够实现一种技术:程序员使用一个变量表示类型由编译器推导,编译器在编译的时候回去类型信息,再返回将类型覆盖到变量中,就可以实现类似动态语言的能力。C++ 11 中,这个技术就叫类型推导。

  这里我们使用了 auto 关键字来要求编译器对变量 name 的类型进行自动推导。编译器根据它的初始化表达式类型,推导出 name 的类型为 char*。
  auto 关键字在早起 C/C++ 标准中有着完全不同的定义。声明时使用 auto 修饰的变量,按照早起 C/C++ 标准的解释,是具有自动存储期的局部变量。但是现实中几乎没有人使用,因为一般函数内没有声明为 static 变量总是具有自动存储期的局部变量。
  在 C++ 11 中,重写了 auto 的定义:auto 声明变量的类型必须由编译时期推导而得。

  auto 声明的变量必须被初始化,以使编译器能够从其初始化表达式中推导出其类型。从这个意义上来讲,auto 并非一种“类型”说明,而是一个类型声明时的“占位符”,编译器在编译时期会将 auto 替代为变量实际的类型。
  auto 推导的最大优势是在拥有初始化表达式的复杂类型变量时简化代码,比如 STL 库的各种容器的迭代器。
 

auto 推导规则

  • 对于指针类型,声明为 auto、auto* 是没有区别的。
  • 声明引用类型,必须使用 auto &。
  • 不能得到推导变量的 const、volatile 属性

  总的来说,auto 在 C++ 11 中是相当关键的特性之一。比如追踪返回类型的函数声明、lambda 和 auto 的配合使用等。auto 只是 C++ 11 类型推导的一部分,还有一部分应该使用 decltype 来体现。

decltype

  C++ 98 标准中就部分支持动态类型,使用的是运行时类型识别(RTTI)。
  RTTI 的机制是为每个类型产生一个 type_info 类型的数据,使用 typeid( * ) 返回相应变量的 type_info 数据。type_info 的 name 成员函数可以返回类型的名字,返回字符串为数字加字符的组合如:3int,6double。在 C++ 11 中增加了 hash_code 成员函数,返回该类型的哈希值,但是标准中并不保证唯一,可以使用 operator== 来判断类型相同。
  但是在实际使用中,程序员需要的是使用这样的类型而不是识别该类型,因此 RTTI 无法满足需求。C++ 因此在新标准中增加了 auto 和 decltype 关键字。

 

decltype 推导规则

  规则推导依次进行,满足任一条件就退出推导。
– 单个标记符和类成员表达式,推导为 T。
– 右值引用,推导为 T&&。
– 非单个标记符的左值,推导为 T&。
– 不满足以上三个条件,则推导为 T。

  所以,对于 int i,编译器 decltype(i) 和 decltype((i)) 的编译结果不同,因为 i 是单个标记符,推导为 int;(i) 是标记符加小括号,不满足规则一,但满足规则三,推导为 int &。如果 decltype((i)) 没有给予左值 int 就会报错——左值引用未初始化。

const、volatile 继承

  decltype 与 auto 不同,decltype 是可以带走 const 和 volatile 限制符的,但是如果类的实例是使用 const 限制的,而类的成员定义没用 const 限制,则 decltype 推导的结果是不带 const 定义的,volatile 也是这样。
  decltype 也可以手动设定 const、volatile、&、&& 等,如果推导出来的类型是 const 的,则声明 const 会被忽略掉,手动增加的左值引用或者右值引用符也会满足引用折叠。

  对于 var5,const 和 volatile 是可以同时修饰一个变量的。const 修饰的变量是一个编译期的限制,编译期可能不会为 const 变量分配内存,意在限制本程序修改这个值;volatile 修饰的变量在编译期告诉寄存器不要做类似 const 的优化,一定要为变量分配内存,可用于其他程序可以修改本程序的值。而同时加上 const 和 volatile,则表示,请为这个常量分配内存,虽然本程序不会改变它,但是其它程序可能会改变它。

追踪返回类型

  追踪返回类型配合 auto 与 decltype 会真正释放 C++ 11 中泛型编程的能力。早期的 C++ 11 在使用模板的时候可能会出现尴尬的场景。如下所示,应该返回什么样的类型呢?只有返回值不同的函数不能参与重载,所以到最后考虑到完备性就会多些很多冗余的代码。

  在这里也不能用 decltype(t + u) 来替代 ???,因为编译函数时的顺序是从左到右。而函数使用前必须先声明。所以就需要 C++ 11 的两个关键字来配合使用,实现使用 auto 占位,使用 decltype 来推导类型,再将类型填入 auto 中。

  所以,auto 占位符和 ->return_type 也就构成了追踪返回类型函数的两个基本元素。

追踪返回类型可以用到的地方

替代普通函数

  对于普通函数来说,追踪返回类型会明显复杂一些。比如下面两个函数意义完全一样。

  但是,对于多层命名空间嵌套的变量,还是有一定简化作用。因为追踪返回类型默认处于函数的命名空间或者类名之下的。

  返回值类型也就不需要写成 OuterType::InnerType 的形式。

简化函数定义

  C++ 有时会返回函数指针,这时候代码的可读性就会非常非常差。

  在面试题有时会有这样的考题。如果能够使用追踪返回类型简化这个考题,那么通过的概率也就大得多。

  追踪返回类型只需要依照从右往左的方式,就可以将嵌套的声明解析出来。大大地提高了可读性。

 
转载请带上本文永久固定链接:http://www.gleam.graphics/type-deduction.html

About the Author

说点什么

您将是第一位评论人!

avatar