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;
    ~~~

##快速笔记

  1. 三个不用auto就感到有点困扰的使用小场景,用auto神清气爽。而且,C++14中lambda的形参也可以包含auto了,更加方便。

    • 只声明却忘了初始化
    • Iterator所指的对象的类型声明
    • 声明闭包(closure)的类型
  2. 指定闭包类型的话,用std::function对象也可以。但是这个方法通常会比auto占用空间更多,速度更慢。

    • std::function 语法冗长,需要重复的类型声明
    • std::function 通常比 closure 要大。拥有固定大小,再在heap中分配内存来保存额外数据。可能抛出out-of-memory 异常(exception).
    • 归因于限制inline的实现细节和产生的间接函数调用(indirect function call), 调用一个std::function对象比调用一个auto声明的对象,几乎必然要慢。
  3. 除了上述优点,auto还能避免”type shortcuts”问题。两个例子
    • std::vector<int>::size_type
    • std::pair<const std::string,int>
  4. 如果在你的专业判断下,觉得自己的代码用显式类型声明更清晰更加可维护,那可以继续这样使用。但是要记住:在采纳编程语言世界中被广泛知晓的类型推断(type inference)时,C++并没有开辟新的战场。得益于动态类型语言(Perl, Python, Ruby)的成功, 在类型推断被开发社区广泛体验以后,可以说这个技术与创造维护大规模,工业强度的代码基底没有冲突。

  5. 关于”瞄一眼”的快速可读性下降的担忧
    • 有IDE可以查看类型
    • 很多情况下更加抽象的类型认知和确切的类型一样有用。对于这种只需要知道抽象类型的情况,精良选择的变量名称也可以提供这些信息。
  6. 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 //as before void dwim(It b, It e) { for (; b != e; ++b) { auto currValue = *b; ... } } ~~~

再来看看闭包的情况
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&, const std::unique_ptr&) ~~~ 那么你就需要如下的声明 ~~~ std::function<bool(const std::unique_ptr&, const std::unique_ptr&)> func; ~~~ 也就是说在C++11中我们不借助auto也可以用如下语句声明这个derefUPLess, ~~~ std::function<bool(const std::unique_ptr&, const std::unique_ptr&)> derefUPLess = [](const std::unique_ptr& p1, const std::unique_ptr& p2) { return *p1 < *p2; }; ~~~

==Example 3==
~~~
std::vectorv; ... unsigned sz = v.size(); ~~~ v.size()的官方返回类型是`std::vector::size_type`, 但是很少开发者意识到这是unsigned integral类型。很多开发者认为这里使用unsigned已经足够好了。在32位系统中他们有同样的大小。但是在64位系统中,unsigned是32位的,但是`std::vector::size_type`是64位的.这会带来一些移植问题。 使用auto的话就不用理会这些问题了 ~~~ auto sz = v.size(); //sz's type is std::vector::size_type ~~~

再来考虑另一个例子
~~~
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描述。