条款5 优先使用auto而非显式声明
使用显式类型声明有如下潜在的问题,举个栗子:
容易写出上面这样的代码——忘记初始化x
,因此它的值是无法确定的。也许它会被初始化为0
。
再举个栗子:
需要显式指明typename std::iterator_traits<It>::value_type
来表示被迭代器指向的值得类型,这并不是使用C++
编程本该有的愉悦体验。
由于C++11
,得益于auto
,这些问题都消失了,auto
变量从它们的初始化推导出其类型,所以它们必须被初始化。
由于auto
使用类型推导,它还可以表示那些仅仅被编译器知晓的类型:
在C++14
中,模板被进一步丢弃,因为使用lambda
表达式的参数可以包含auto
:
也许你在想,我们不需要使用auto
去声明一个持有封装体的变量,因为我们可以使用一个std::function
对象。
std::function
是C++11
标准库的一个模板,它可以使函数指针普通化。鉴于函数指针只能指向一个函数,然而,std::function
对象可以应用任何可以被调用的对象,就像函数。声明一个名为func
的std::function
对象,它可以引用有如下特点的可调用对象:
你可以这么写:
因为lambda
表达式得到一个可调用对象,封装体可以存储在std::function
对象里面。这意味着,我们可以声明不使用auto
的C++11
版本的derefUPLess
如下:
使用std::function
和使用auto
并不一样。一个使用auto
声明持有一个封装的变量和封装体有同样的类型,也仅使用和封装同样大小的内存。持有一个封装体的被std::function
声明的变量的类型是std::function
模板的一个实例,并且对任何类型只有一个固定大小。这个内存可能不能满足封装体的需求。出现这种情况时,std::function
将会开辟堆空间来存储这个封装体。导致的结果就是std::function
对象一般会比auto
声明的对象使用更多的内存。由于实现细节中,约束inline
的使用和提供间接函数的调用,通过std::function
对象来调用一个封装体比通过auto
对象要慢。换言之,std::function
方法通常体积比auto
大,且慢,还有可能导致内存不足的异常。
auto
的优点除了可以避免未初始化的变量,变量声明引起的歧义,直接持有封装体的能力。还有一个就是可以避免“类型截断”的问题。举个栗子:
v.size()
定义的返回类型是std::vector<int>::size_type
,但是很少有开发者对此十分清楚。std::vector<int>::size_type
被指定为一个非符号的整数类型,因此很多程序员认为unsigned
类型是足够的,然后写出了上面的代码。这将导致一些有趣的后果。比如说在32位windows
系统上,unsigned
和std::vector<int>::size_type
有同样的大小,但是在64位的windows
上,unsigned
是32位的,而std::vector<int>::size_type
是64位的。这意味着上面的代码在32位windows
系统上工作良好,但是在64位windows
系统上有时可能不正确,当应用程序从32位移植到64位上,这就比较浪费时间了。使用auto
可以保证你不必被上面的东西所困扰:
再看如下的代码:
这看上去完美合理。但是有一个问题,意识到std::unordered_map
的key
部分是const
类型的,在哈希表中std::pair
的类型不是std::pair<std::string, int>
,而是std::pair<const std::string, int>
。但是这不是循环体外变量p
的声明类型。后果就是,编译器竭尽全力找到一种方式,把std::pair<const std::string, int>
对象转化为std::pair<std::string, int>
对象。这个过程将通过复制m
的一个元素到一个临时对象,然后将这个临时对象和p
绑定完成。在每个循环结束的时候这个临时对象将被销毁。最终这个代码的行为将会令人吃惊,因为你本来想简单的将引用p
和m
的每个元素绑定的。当然这种无意的类型不匹配还是可以通过auto
解决:
归纳
auto
变量一定要被初始化,并且对由于类型不匹配引起的兼容和效率问题有免疫力,可以简单化代码重构,一般会比显式的声明类型敲击更少的键盘auto
类型的变量也受限于条款2和条款6中描述的陷阱
Last updated