条款6:auto 推导的型别不符合要求时,使用带显式型别的初始化物习惯用法
虽然 auto 是一个强大的工具(如条款5所述),但它推导出的类型有时会与程序员的直觉或意图不符。这种情况尤其容易发生在涉及“隐形”代理类(proxy class)的表达式中。
什么是代理类: 在 C++ 中,代理类(Proxy Class)是一种设计模式的实现,属于结构型设计模式的一种。它的主要作用是为某个对象提供一个替代者或中介,以控制对该对象的访问。这个模式被称为代理模式(Proxy Pattern)。广为人知的std::shared_ptr即是这一类
auto遇到代理类
下面使用 std::vector<bool>
作为核心示例。假设我们有一个函数返回
std::vector<bool>,并且我们想获取第五个元素,
下面的代码可以正常运行。
1 | `std::vector<bool>` features(const Widget& w); |
1 | auto highPriority = features(w)[5]; // 类型推导 |
std::vector<bool>的代理类,
而不是bool类型。
std::vector<bool>
是标准库中的一个特化版本,它为了节省空间,将每个布尔值存储为一个比特位。由于
C++ 禁止对单个比特位(bit)进行引用,因此
std::vector<bool> 的 operator[] 并不会返回
bool&。相反,它返回一个扮演 bool&
角色的代理类对象,这个类的类型是
std::vector<bool>::reference。
这个 std::vector<bool>::reference
代理对象被设计为可以隐式转换为
bool(即它所代表的比特位的值)。对于前一类代码bool highPriority = ...,在这行代码中,features(w)[5]
返回一个 std::vector<bool>::reference
对象。为了初始化 bool 类型的变量 highPriority,这个代理对象会执行向 bool
的隐式类型转换。因此,highPriority 得到了第5个比特位的值,一切正常。
而在后者中, features(w) 返回的是一个临时对象 (一个右值
std::vector<bool>)。且operator[] 返回的
std::vector<bool>::reference 代理对象(现在被
highPriority
持有)其内部实现通常包含一个指向那个临时向量所管理的机器字的指针。在
auto highPriority = ... 这条语句的末尾,features(w)
返回的临时 std::vector<bool> 对象被析构了。这导致
highPriority(那个代理对象)现在内部持有一个空悬指针 (dangling
pointer)。当这个含有空悬指针的 highPriority 对象在后续代码(如
processWidget)中被使用时,就会产生未定义行为。
因此,
我们要避免写出auto someVar = 隐形”代理型别表达式的代码。
解决方案:带显式型别的初始化物习惯用法
auto 本身并不是问题所在;问题在于它推导出的类型(代理类)不是我们想要的类型(值 bool)。
条款6提出的解决方案是继续使用 auto,但通过显式类型转换来“引导” auto 推导出我们想要的类型。这种方法被称为“带显式类型的初始化物习惯用法”(explicitly typed initializer idiom)。
1 | // 通过 static_cast<bool>(...) 强制调用该代理对象的向 bool 的类型转换运算符 |
这种习惯用法不仅限于解决代理类问题,它还可以用来明确表达程序员的意图,使那些故意的类型转换在代码中更加清晰可见