Use the explicitly typed initializer idiom when auto
deduces undesired types
尽管 auto 相对于显式声明类型好处很多, 但还是有一些棘手的陷阱需要注意。
假如我有一个函数,参数为Widget
,返回一个std::vector<bool>
,这里的bool
表示Widget
是否提供一个独有的特性。我们可以写这样的代码:
1 | std::vector<bool> features(const Widget& w); |
以上代码没有问题。但是使用auto
代替highPriority
的显式指定类型,就会出错:
1 | auto highPriority = features(w)[5]; //w高优先级吗? |
为什么是一个未定义行为呢?std::vector<bool>
以紧凑的形式存储它的bool
,每个bool
占一个bit。std::vector<T>
的operator[]
理应返回一个T&
,但是C++禁止对bit
s的引用。因此,有一个返回的代理对象(proxy)来模仿 bool 的行为。std::vector中operator[]的返回类型其实是std::vector::reference。
在第一个版本中,features
返回的std::vector<bool>::reference
对象被隐式转换赋值给bool
变量highPriority
。
而在第二个版本中,auto
推导highPriority
的类型为std::vector<bool>::reference
,包含一个指向这些bits的指针。highPriority
是这个std::vector<bool>::reference
的拷贝,所以也包含一个指针。在这个语句结束的时候std::vector<bool>::reference
将会被销毁,因此作为临时变量的highPriority
包含一个悬置的指针,会造成未定义行为。
事实上, “代理(Proxy)”的设计模式是软件设计模式中历史最悠久的成员之一。一些代理类被设计于用以对客户可见。比如std::shared_ptr
和std::unique_ptr
。其他的代理类则或多或少不可见,比如std::vector<bool>::reference
。
不可见代理类里一些C++库用了表达式模板的黑科技,能够提高数值运算的效率。举个例子:
1 | Matrix sum = m1 + m2 + m3 + m4; |
对两个Matrix
对象使用operator+
将会返回如Sum<Matrix, Matrix>
这样的代理类而不是直接返回一个Matrix
对象。上述表达式的结果可能是 Sum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix>
, 如果需要具体求值时再一次性将其转换为 Matrix 对象,节省了计算次数。
不可见的代理类通常不适用于auto
,这样类型的对象的生命期通常不能活过一条语句。 更好的解决方案是使用显式类型初始器惯用法:使用auto
声明一个变量,然后对表达式强制类型转换(cast)得出期望的推导结果。
1 | auto highPriority = static_cast<bool>(features(w)[5]); |
重点
- 不可见的代理类(proxy)可能会导致 auto 从表达式推断出“错误”的类型。
- 显式类型初始器惯用法强制
auto
推导出你想要的结果。