Item 6: 当auto推导出一个不想要的类型时,使用显式类型初始化的语法

本文翻译自《effective modern C++》,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!

博客已经迁移到这里啦

Item 5解释了比起显式指定类型,使用auto来声明变量提供了大量技术上的优点,但是有时候auto的类型推导出zigs(这个类型),但是你想要的是zag(另外一个类型)。举个例子,假设我有一个函数以Widget为参数并且返回一个std::vector,每个bool指示Widget是否提供了特定的特性:

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

进一步假设bit 5指示了Widget有高优先级。因此我们写了如下的代码:

Widget w;
...
bool highPriority = features(w)[5];
...
processWidget(w, highPriority); //根据w的优先级处理w

这段代码没有错。它工作得很好。但是如果我们做一些看起来无害的操作,也就是用auto来替换highPriority的显式类型声明,

auto highPriority = features(w)[5];

情况改变了。所有的代码将能继续通过编译,但是它的行为不再和预测的一样了:

processWidget(w, highPriority); //未定义的行为!

就像注释指示的那样,调用processWidget现在是未定义的行为。但是为什么会这样?回答有可能很奇怪。在使用auto的代码中,highPriority的类型不再是bool、尽管std::vector概念上保存了bools,std::vector的operator[]不返回容器元素的引用(除了bool类型,其他的std::vector::operator[]都这么返回)。作为替换,它返回一个类型是std::vector::reference的对象(一个类内部装了std::vector)。

std::vector::reference之所以会存在是因为std::vector被指定来代表它包装的bools,每个bool占一bit。这将造成一个问题,对于std::vector的operator[],因为对于std::vector,operator[]被假定要返回T&,但是C++禁止返回bits的引用。因为没有可能返回一个bool&,operator[]为std::vector返回一个对象,这个对象表现得像bool&一样。为了这个行为能成功,在本质上,std::vector::reference对象必须能被用在任何bool&能用的地方。在C++的众多特性中,能让std::vector::reference这么工作的是一个到bool的隐式转换。(不是bool&到bool,对于std::vector::reference如何效仿bool&,如果要解释清楚所有的技术那就走远了,所以我只是简单地谈论了众多技术中的一个,隐式转换。)

记住了这些信息后,再看看源代码的这一部分:

bool highPriority = features(w)[5]; //显式声明
                                    //highPriority的类型

这里,features返回一个std::vector对象,它的operator[]被调用。operator[]返回一个std::vector::reference对象,根据highPriority初始化的需要,这个对象将会隐式地转换为bool。因此highPriority最后代表features返回的std::vector中的bit 5的值(就像它支持的那样)。

对比对highPriority使用auto初始化声明时发生的情况:

auto highPriority = features(w)[5]; //推导highPriority的类型

再一次,features返回一个std::vector对象,并且,再一次,operator[]被调用。operator[]继续返回一个std::vector::reference对象,但是现在这里有些变化,因为auto推导highPriority的类型为std::vector::reference。highPriority不再拥有features返回的std::vector中的bit 5的值。

它拥有的值依赖于std::vector::reference是怎么实现的。一种实现是,这些对象包含一个指针指向一个机器字节(word)的引用,以及一个代表特定bit的偏移。考虑一下,这样一个std::vector::reference的实现,对于highPriority的初始化意味着什么。

对于features的调用返回一个std::vector临时对象。这个对象没有名字,但是为了讨论的目的,我讲把它叫做temp。operator[]是对temp的调用,并且std::vector::reference返回一个对象,这个对象指向一个字节(word),这个被temp管理的数据结构中的字节持有所需的bit,加上对象存储的偏移就能找到bit 5。highPriority是这个std::vector::reference对象的一份拷贝。所以highPriority也持有指向temp中某个字节(word)的指针,以及bit 5的偏移。在语句的最后,temp销毁了,因为它是一个临时对象。因此,highPriority持有的是一个悬挂的指针,并且这就是在processWidget调用中造成未定义行为的原因:

processWidget(w, highPriority); //未定义行为!
                                //highPriority持有悬挂的指针

std::vector::reference是一个代理类的例子:一个类的存在是为了效仿和增加其他类型的行为。代理类被用作各种各样的目的。std::vector::reference为了提供一个错觉:std::vector的operator[]返回一个bool引用。再举个例子,标准库的智能指针是一个对原始指针进行资源管理的代理类。代理类的用法已经慢慢固定下来了。事实上,设计模式“Proxy”就是软件设计模式万神殿(Pantheon)中长期存在的一员。

对于客户来说,一些代理类被设计成可见的。举个例子,这就是std::shared_ptr和std::unique_ptr的情况。另外一种代理类被设计成或多或少不可见的。std::vector::reference就是这种“不可见”代理类的例子,对于它的同胞std::bitset也有这样的代理类:std::bitset::reference。

同样在这个阵营中,一些C++库中的类使用一种叫expression template的熟知科技。这样的库最初开发出来是为了提升算术运算代码的效率。给出一个类Matrix和Matrix对象m1,m2,m3和m4,举个例子,表达式

Matrix sum = m1 + m2 + m3 + m4;

能被计算得更加有效率一些,只需要让Matrix对象的operator +返回一个结果的代理来代替结果本身。也就是,两个Matrix对象的operator +将返回一个对象,这个对象用像Sum

            你要记住的事
  • 对于一个初始化表达式,“看不见的”代理类型能造成auto推导出“错误的”类型
  • 显式类型初始化语法强制auto推导出你想要它推导的类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值