《Effective Modern C++》学习笔记 - Item 5: 倾向于使用auto而不是显式类型声明

  • 先来定义一个简单的局部变量:
int x; // 糟糕,忘记初始化了。x也许会被初始化为0也许不会,取决于上下文。
  • 别在意。再来定义一个用迭代器(Iterator)解引用初始化的局部变量:
template<typename It>
void dwim(It b, It e)
{
    while (b != e)
    {
    	// 真的假的?声明一个变量这么麻烦?
        typename std::iterator_traits<It>::value_type currValue = *b;
    }
}
  • 事不过三,再举个例子:声明一个闭包的局部变量。坏了,闭包的类型只有编译器知道。
  • 写C++一点都不快乐!(笔者注:原文如此,笑死,加粗表扬)

auto的优势

  • 在C++11,由于 auto 的出现,以上这些问题都不复存在。auto 由initializer推断类型,因此不会出现变量未初始化问题。
auto x1; 		// error!
auto x2 = 0; 	// well-defined.

template<typename It>
void dwim(It b, It e)
{
    while (b != e)
    {
        auto currValue = *b; // 舒服了
    }
}
// 可以表示只由编译器知道的类型
auto derefUPLess =
   [](const std::unique_ptr<Widget>& p1,
      const std::unique_ptr<Widget>& p2)
   { return *p1 < *p2; };
  • 在C++14中,lambda表达式的参数也可以使用 auto
auto derefUPLess =
   [](const auto& p1,
      const auto& p2)
   { return *p1 < *p2; };
  • 你对第三个例子也许会想到使用std::function。概念:C++11对函数指针概念的泛化,可以指向任何可调用的(callable)对象(注:如重载了operator()的类,俗称functor)。声明时在模板中给出指向函数的签名(signature):
// 不使用auto的版本
std::function<bool(const std::unique_ptr<Widget>&,
                   const std::unique_ptr<Widget>&)>
  derefUPLess = [](const std::unique_ptr<Widget>& p1,
                   const std::unique_ptr<Widget>& p2)
                  { return *p1 < *p2; };
// tmd, C++怎么会变成这个样子.jpg
  • 且不论语法上的繁杂性,使用 std::functionauto 是不同的auto 声明的闭包变量其类型就是自身,也只占用闭包需求的内存量。而 std::function 创建的是一个持有闭包对象的 std::function 对象,这通常会占用更多的内存,而且调用速度几乎肯定会更慢。本场比赛中,auto 完胜。

  • auto 的优势还不只如此。以下程序是你可能见过(或者写过的):

std::vector<int> v;
unsigned sz = v.size();
  • v.size() 返回值的正式形式为 std::vector<int>::size_type,然而很少开发者真正了解这一点,而往往认为用 unsigned 足够了。在32位机器上,两者大小相同;然而在64位机器上,前者是64位而后者是32位!
    《Effective Modern C++》学习笔记 - Item 5: 倾向于使用auto而不是显式类型声明
  • 注:相信更多人(包括我)更习惯使用 size_t 的写法,这是没有问题的(见下图)。原书中没有提到这一点。
    《Effective Modern C++》学习笔记 - Item 5: 倾向于使用auto而不是显式类型声明
  • 显式类型声明可能带来另一种隐患:
std::unordered_map<std::string, int> m;
m.insert(std::make_pair("123", 1));
m.insert(std::make_pair("234", 2));
for (const std::pair<std::string, int>& p : m)
{
    ... // do something with p
}
  • 一眼看上去没什么问题不是吗?但是你可能已经忘记了一个事实:std::unordered_map 的键部分是 const,因此哈希表中的元素不是 std::pair<std::string, int>(声明的 p 的类型),而是 std::pair<const std::string, int>!于是,编译器会想办法将后者转换为前者,并且还真的有一种方案(因此不会报错):把后者复制到一个临时对象,再降其引用赋给 p。这绝对不是你想要的效果。用 auto 可以解决这一切问题:
    《Effective Modern C++》学习笔记 - Item 5: 倾向于使用auto而不是显式类型声明

auto的不足

  • Item 2讨论了有时 auto 推断出的类型可能不是你想要的一些情况。
  • auto 可能会带来代码可读性上的问题。IDE的类型提示和命名风格良好的变量名称可以缓解这一问题。
  • 归根结底,auto 只是你的一个可选工具。因此还是要根据实际场景合适地利用其强大功能。

总结

  1. auto 变量必须被初始化,几乎能解决所有移植性和效率上的问题,还能简化代码重构的过程,同时能减少你的打字量。
  2. auto 类型的变量容易掉入Item 2和6所描述的坑中。
上一篇:【计算机网络】P1-P2


下一篇:【microbit】micropython 之 体感遥控器