当auto推导的类型不符合要求时,使用带显式类型的初始化物习惯用法
举个例子,假设有一个函数接受一个Widget并返回一个std::vector<bool>
,其中每一个bool
元素都代表着Widget是否提供一种特定功能;
Widget w;
std::vector<bool> features(const Widget& w);
//第5个比特代表的意思是:Widget是否具有高优先级
bool highPriority = features(w)[5]; //w具有高优先级么?
processWidget(w, highPriority); //按照w的优先级来处理
上面这段代码没什么问题。
如果我们把highPriority从显式类型改成auto
。
auto highPriority = features(w)[5]; //w具有高优先级么?
processWidget(w,highPriority); //未定义行为
为什么会出现未定义行为,因为在使用了auto
之后,highPriority的类型不再是bool
了。尽管从概念上来说,std::vector<bool>
应该持有的是bool
类型的元素,但是std::vector<bool>
的operator[]
的返回值并不是容器中的一个元素的引用(对于其他所有形参类型而言,std::vector::operator[]
都返回这样的值,单单bool
是个例外),它返回的是std::vector<bool>::reference
类型的对象。
之所以整出个std::vector<bool>::reference
,是因为std::vector<bool>
做过特化,用了一种压缩形式表示其持有的bool
元素,每个bool
元素用一个比特来表示。这种做法给std::vector<bool>
的operator[]
带来了一个问题,因为按理来说,std::vector<T>
的operator[]
应该返回一个T&
,然而C++中禁止比特的引用。既然不能返回一个bool&
,std::vector<bool>
的operator[]
转而返回了一个表现的像bool&
的对象。std::vector<bool>
类型的对象就要在所有能用bool&
的地方保证它们也能用。实现这个效果的原理是,std::vector<bool>::reference
做了一个向bool
的隐式类型转换。
bool highPriority = features(w)[5]; //显式声明highPriority的类型
这里,features返回了一个std::vector<bool>
对象,然后针对该对象执行operator[]
。然后,operator[]
返回一个std::vector<bool>::reference
类型的对象,该对象紧接着被隐式转化为一个初始化highPriority所需的bool
对象。
auto highPriority = features(w)[5];
features返回了一个std::vector<bool>
对象,然后针对该对象执行operator[]
。然后,operator[]
返回一个std::vector<bool>::reference
类型的对象,auto
会把highPriority的类型推导成std::vector<bool>::reference
。这么一来,highPriority的值就完全不可能是features所返回的std::vector<bool>
对象的第5个比特。
在后一种情况下,highPriority的值取决于std::vector<bool>::reference
的实现。有一种实现让对象含有一个指针,指向一个机器字,该机器字持有那个被引用的比特,再加上基于那个比特对应的字的偏移量。
对features的调用会返回一个std::vector<bool>
类型的临时对象。该对象没有名字。我们成为temp。针对temp执行operator[]
,返回一个std::vector<bool>::reference
类型的对象,该对象含有一个指向机器字的指针,该机器字在一个持有temp所管理的那样比特的数据结构中,还要加上第5个比特所对应的机器字的偏移量。由于highPriority是std::vector<bool>::reference
对象的一个副本,所以highPriority也含有一个指向temp中的机器字的指针,还要加上第5个比特所对应的机器字的偏移量。
在表达式结束处,temp会被析构,因为它是一个临时对象。结果,highPriority会含有一个空悬指针,最终导致调用processWidget时出现未定义行为。
processWidget(w,highPriority); //未定义行为,highPriority含有空悬指针
std::vector<bool>::reference
是个代理类的实例。所谓代理类,就是指为了模拟或增广其他类型的类。
一个普遍的规律是,“隐形”代理类无法与auto和平共处。
所以,你要防止写出这样的代码:
auto someVar = "隐形"代理类型表达式
对于代理类,使用auto
时,只要进行一次类型转换就可以避免上述问题。
auto highPriority = static_cast<bool>(features(w)[5]);
此处,feature(w)[5]
仍然返回一个std::vector<bool>::reference
对象,但是强制类型转换将这个表达式的类型转换成了bool
,从而auto
就将highPriority推导成了该类型。
要点速记
- “隐形”的代理类型可以导致
auto
根据初始化表达式推导出“错误的”类型 - 带显式类型的初始化物习惯用法强制
auto
推导出你想要的类型