条款5 优先使用auto而非显式声明
使用显式类型声明有如下潜在的问题,举个栗子:
int x;容易写出上面这样的代码——忘记初始化x,因此它的值是无法确定的。也许它会被初始化为0。
再举个栗子:
template<typename It>
void dwim(It b, It e) {
while (b != e) {
typename std::iterator_traits<It>::value_type currValue = *b;
...
}
}需要显式指明typename std::iterator_traits<It>::value_type来表示被迭代器指向的值得类型,这并不是使用C++编程本该有的愉悦体验。
由于C++11,得益于auto,这些问题都消失了,auto变量从它们的初始化推导出其类型,所以它们必须被初始化。
int x1; // 未初始化,且能通过编译
auto x2; // 不能通过编译
auto x3 = 0; // 能通过编译,运行良好
template<typename It>
void dwim(It b, It e) {
while (b != e) {
auto currValue = *b;
...
}
}由于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