Item 5: Prefer auto to explicit type declarations.
Sunday, 27. August 2017 03:02PM
#Item 5: Prefer auto to explicit type declarations.
#条目5:宁愿使用 auto 替代 explicit type declarations(显式类型声明)
##术语&概念
- ###explicit type declaration
用具体类型声明的方式
Example
~~~
int x;
~~~
- ###auto声明法
~~~
auto x3 = 0;
~~~
##快速笔记
-
三个不用auto就感到有点困扰的使用小场景,用auto神清气爽。而且,C++14中lambda的形参也可以包含auto了,更加方便。
- 只声明却忘了初始化
- Iterator所指的对象的类型声明
- 声明闭包(closure)的类型
-
指定闭包类型的话,用std::function对象也可以。但是这个方法通常会比auto占用空间更多,速度更慢。
- std::function 语法冗长,需要重复的类型声明
- std::function 通常比 closure 要大。拥有固定大小,再在heap中分配内存来保存额外数据。可能抛出out-of-memory 异常(exception).
- 归因于限制inline的实现细节和产生的间接函数调用(indirect function call), 调用一个std::function对象比调用一个auto声明的对象,几乎必然要慢。
- 除了上述优点,auto还能避免”type shortcuts”问题。两个例子
std::vector<int>::size_type
std::pair<const std::string,int>
-
如果在你的专业判断下,觉得自己的代码用显式类型声明更清晰更加可维护,那可以继续这样使用。但是要记住:在采纳编程语言世界中被广泛知晓的类型推断(type inference)时,C++并没有开辟新的战场。得益于动态类型语言(Perl, Python, Ruby)的成功, 在类型推断被开发社区广泛体验以后,可以说这个技术与创造维护大规模,工业强度的代码基底没有冲突。
- 关于”瞄一眼”的快速可读性下降的担忧
- 有IDE可以查看类型
- 很多情况下更加抽象的类型认知和确切的类型一样有用。对于这种只需要知道抽象类型的情况,精良选择的变量名称也可以提供这些信息。
- auto可以减少微妙的错误,提高正确性和效率。而且重构的时候比较方便。
##例子
==Example 1==
int x;
忘记初始化
template<typename It> // algorithm to dwim ("do what I mean")
void dwim(It b, It e) // for all elements in range from
{ // b to e
for (; b != e; ++b) {
typename std::iterator_traits<It>::value_type
currValue = *b;
...
}
}
类型名字太长
上述两种情况用auto就轻松解决,比如
~~~
int x1; //potentially uninitialized
auto x2; //error! initializer required
auto x3 = 0; //fine, x3’s value is well-defined
template
再来看看闭包的情况
C++11中
~~~
auto derefUPless =
{ return *p1 < *p2; };
~~~
C++14中
~~~
auto derefLess =
{ return *p1 < *p2; };
~~~
==Example 2==
如果说std::function所指的callable object有如下的签名式(signature)
~~~
bool(const std::unique_ptr
==Example 3==
~~~
std::vector
再来考虑另一个例子
~~~
std::unordered_map<std::string, int> m;
…
for (const std::pair<std::string, int>& p : m)
{
… //do something with p
}
~~~
这里看起来很完美,理据服。但是事实上存在一个问题,你发现了么?
出差错的地方在于我们要知道容器std::unordered_map
的key部分是const, 所以这个哈希表(hash tabel)中的std::pair
的类型不是std::pair<std::string,int>
,而是std::pair<const std::string,int>
. 上面这样写的结果就是使得编译器力求找到办法将哈希表中的std::pair<const std::string,int>
对象转换为std::pair<std::string,int>
对象。它们将对哈希表中的每一个对象进行复制创建出临时对象,并且将引用p绑定在上面,从而成功做到转换。
然后你就会对对这段代码的效率感到惊讶,因为你本来几乎很确定地只想把引用p绑定到表中的每个对象上去。
像这样的无意识的类型错配能够用auto避免,就像下面这样
~~~
for (const auto& p : m) {
… // as before
}
~~~
##谨记要点
- auto变量必须初始化。普遍免疫那些会引发移植性和效率问题的类型错配。简化重构(refactoring)过程。通常比显式指定类型需要更少的键盘键入。
- 被auto自动推导类型的变量受制于一些陷阱,这些内容将在Item 2和 Item 6描述。