ROOT
ROOT
文章目录
  1. Item 5: 优先考虑使用 auto 来代替显式类型声明
    1. Things to Remember
  2. Item 6: 当 auto 推导出不想要的类型时,使用显式类型声明
    1. Things to Remember

《Effective Modern C++》第二章笔记:auto

这章更加详细地介绍 auto 关键字。

Item 5: 优先考虑使用 auto 来代替显式类型声明

习惯 C 的写法可能都会为变量指出一个类型,然而这有可能忘记初始化;有些场合若显式指出变量的类型,那么写起来非常长;将局部变量定义成闭包类型,因为闭包类型只有编译器知道,所以无法显式指出它的类型。

因为 C++11 有了 auto 关键字,这些问题很好地解决了。首先用 auto 来定义一个变量的话,必须要初始化,这就避免了变量未初始化的错误;其次能节省输入时间;auto 类型推导也可以指出只有编译器才知道的类型(比如说闭包)。

auto derefUpless =
	[](const std::unique_ptr<Widget> &p1,
	   const std::unique_ptr<Widget> &p2)
	{ return *p1 < *p2; };

在 C++14 中甚至能省略参数的类型:

auto derefUpless =
	[](const auto &p1,
	   const auto &p2)
	{ return *p1 < *p2; };

关于用局部变量来存储闭包,可能会优先考虑 std::function 函数对象,因为函数对象也能存储闭包。比如上面那个闭包,可以这样写:

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

首先可以看出,参数太冗长了,param 都要写两遍。std::function和使用 auto 定义的闭包并不相同。auto 定义的闭包的变量大小和闭包本身一样大,而 std::function 定义的对象来保存闭包都有固定的大小,当闭包过大的时候,它会分配堆空间来存储,有可能产生内存溢出错误,这导致在通常情况下 std::function 对象用的内存比 auto 定义的要多;也由于是间接调用闭包,这也会比 auto 定义的要慢。

虽然有很多理由使用 auto,同时也能避免显式声明的一些坑,但 auto 也有坑,有时候用 auto 推导出来的类型可能并不是自己想要的,这在 Item 2 到 Item 6 介绍过了。

Things to Remember

  • auto 定义的变量必须初始化,能避免一些类型不匹配导致的移植或者性能问题;同时也有利于重构,减少显式声明的代码量。
  • auto 也有坑,在 Item 2 到 6 介绍过了。

Item 6: 当 auto 推导出不想要的类型时,使用显式类型声明

这里介绍了一种情况,使用 std::vector<bool>::operator[] 来检索元素的时候。比如说:

vector<bool> features(const Widget &w); // 返回 widget 对象的属性位向量

Widget w;

bool highPriority = features(w)[5];

process(highPriority);

如果把上面的 bool 换成 auto 来声明的话,那么就会出现未定义行为了。

因为 vector<bool>::operator[] 返回的并不是 bool &,因为 C++ 是按 字节 寻址的,不可能返回一个比特位的地址。为了达到和 bool & 一样的效果,它返回的是一个 vector<bool>::reference 对象,可以隐式转换成 bool 类型。

再来看看使用显式声明的 bool 定义为什么没有问题,因为 operator[] 返回一个 vector<bool>::reference 对象,然后转换成bool,赋值给highPriority,这相当于取第 5 位。

而使用 auto 声明的 highPriority,将是一个vector<bool>::reference 类型的对象。它的值取决于实现,一种实现是 reference 指向内存中包含那一位的字,然后通过位移运算来取得指定位。

因为 features 这个函数返回的是 w 的属性位向量,是一个 临时变量 。当赋值语句结束后,临时变量就销毁了,到后面的process(highPriority) 的时候,highPriority包含悬空指针,导致不确定的行为。

vector<bool>::reference也是代理类 (proxy class) 的一个例子,它为对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。不过 vector<bool>::operator[] 的这个代理类隐藏的比较深,几乎看不出来被代理了。而 C++ 有些代理类设计的比较明显,比如 std::shared_ptrstd::unique_ptr

对于这些隐藏的代理类,就不要直接用 auto 了,因为代理类的对象一般活不长。

当然这并不是 auto 的错,只是它没有推导出想要的类型,可以通过显式类型定义或者强制类型转换来解决。比如说:

auto highPriority = static_cast<bool>(features(w)[5]);

也并不是代理类这么干,比如 auto 推导出自己不想要的类型,可以通过强制类型转换来得到。

Things to Remember

  • 隐藏的代理类可能使得 auto 推导出一个错误的类型
  • 使用强制类型转换来使得 auto 推导出想要的类型
支持一下
扫一扫,支持Netcan
  • 微信扫一扫
  • 支付宝扫一扫