6.当auto推衍类型错误时,使用显式初始化

前言

 
在Item5中曾经提及,使用auto声明变量相较于显式类型声明有许多优势,但有时也会带来问题。


问题实例

 
举例而言,假若我们当前有一个函数,其形参为一个Widget切返回一个std::vector<bool>,每一个bool表征当前Widget是否提供某项功能:

1
std::vector<bool> features(const Widget& w)

进一步地,我们假定bit5代表Widget是否具备权限,因此我们可能会写出如下程序:
1
2
3
Widget w;
bool highPriority = features(w)[5];//is w high priority?
processWidget(w, highPriority);//process w in accord with its priority

上述代码将会运行正常,但如果我们使用auto代替显式的类型声明:
1
2
auto highPriority = features(w)[5];
processWidget(w, highPriority);

上述代码仍然能够通过编译,只是其行为不可预料。原因很简单,vector<bool>返回的并不是一个bool类型,而是一个代理类对象的引用,从而导致上述函数无法被正确运行(详见Effective STL Item18)。
在原始程序中,highPriority发生了一次隐式类型转换,而auto只会如实推衍类型(一个reference-to-bit),不会发生任何类型转换。除此之外,我们在进一步剖析上述程序时会发现,feature(w)生成了一个临时的Vector,highPriority作为其内部元素的一个引用,在语句结束后不可避免地成为了空悬指针,这也就是函数行为不可预料的原因所在。


proxy class

 
不仅仅vector<bool>存在着代理类的情况,标准库中的智能指针类型在某种意义上也是负责管理内存资源的代理类。事实上,设计模式中的代理模式在程序设计中十分流行,有些代理类对于用户而言是显然的,例如std::shared_ptr与std::unique_ptr,但有一些被设计为对用户不可见,例如std::vector<bool>::reference。

除此之外,某些C++库中的class使用了一种名为”expression template”的技术,这项技术的目的主要在于提高数学计算效率。举例而言,现有矩阵计算表达式如下:

1
Matrix sum = m1 + m2 + m3 + m4;

如果使用了”expression template”技术,那么operator+返回的并非是计算结果,而是一个代理类,例如Sum<Matrix,Matrix>之类(此技术的详细说明详见More Effective C++ Item17)。operator=执行了一次隐式类型转换,但如果我们采用auto声明变量,恐怕会得出sum的类型为Sum<Sum<Sum<Matrix, Matrix>,Matrix>, Matrix>。

隐性代理类与auto配合不甚默契,因为它们的生存期往往十分短暂(语句结束即完成析构),因此创建这些类型的变量往往会违反基本设计原则,其往往带来未定义后果。


解决方案

1
auto someVar = expression of "invisible" proxy class type;

这种写法固然不可取,但如何避免出现这类表达式呢?

首先,我们应当学习如何找到隐性代理类。尽管它们难以被开发者所察觉,但库通常会记录下它们的行为,你对库越熟,那么你越不可能忽视这些代理类的使用。即使文档中没有给出介绍,头文件也会忠实地反应出代理类的使用情况,例如vector<bool> operator[]声明:

1
2
3
4
5
6
7
8
9
10
namespace std { // from C++ Standards
template <class Allocator>
class vector<bool, Allocator> {
public:

class reference { … };
reference operator[](size_type n);

};
}

我们可以一目了然地发现vector<bool>返回了一个代理类。

在实际开发过程中,许多人只有在他们试图追踪编译问题或不正确的单元测试结果时才会发现使用代理类的存在。但不管你是怎么找到它们的,即使auto推衍得到了某个代理类类型,但我们仍然没有放弃使用auto的理由。auto本身并不是问题,问题是auto无法推衍出我们想要的类型,因此解决方案很简单:我们强制auto推衍为正确的类型,这种手法被本书作者称为“the explicitly typed initializer”。 idiom”。其基本表现形式如下:

1
2
auto highPriority = static_cast<bool>(features(w)[5]);
auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);


总结

  1. 隐性代理类可能会导致auto推衍出“错误”的类型。
  2. 我们可以使用the explicitly typed initializer加以修正。