Effective Modern C++ Item 6 当auto推导的型别不符合要求时,使用带显示型别的初始化习惯用法

这里列举一个auto会出现推导错误的典型场景,代码如下:

std::vector<bool> features(const Widget& w);
Widget w;
...
bool highPriortiy = features(w)[5];     //w具有高优先级吗?
...
processWidget(w, highPriortiy);         //按照w的优先级来处理之

这上面的代码运行起来都很正常,但是将highPriortiy型别声明的时候换成auto,则后面就会出现未定义的行为了。

std::vector<bool> features(const Widget& w);
Widget w;
...
auto highPriortiy = features(w)[5];     //w具有高优先级吗?
...
processWidget(w, highPriortiy);         //未定义行为!

这里auto推导出来的型别不再是bool,而是std::vector<bool>::reference。之所以要弄出一个std::vector<bool>::reference 是因为std::vector<bool>在存储的时候,采用了压缩形式的表示方法,每个bool元素有用一个比特来表示。这种优化方式给std::vector<bool>operator[]带来了一个问题。因为按照std::vector<T>operator[]应该返回一个T&,但C++禁止比特的引用

那么解决方案是这样的,std::vector<bool>operator[]返回了一个表现的像bool&的对象,即std::vector<bool>::reference。这里不做细究,大概就是把std::vector<bool>::reference做了一个向bool的隐式型别转换。

我们来分析这两节代码的背后逻辑:

  • 对于正常情况非auto下,features返回了一个std::vector<bool>对象,然后针对该对象执行operator[],返回一个std::vector<bool>::reference对象。然后此对象隐式转换为初始化highPriority所需的bool对象。

  • 对于auto情况下,features返回了一个std::vector<bool>对象,然后针对该对象执行operator[],返回一个std::vector<bool>::reference对象。从这里就不一样了auto会把highPriority的型别推导成std::vector<bool>::reference。这么一来,后续的路就完全跑偏了,而且这个跑偏后的结果取决于std::vector<bool>::reference的实现方式。

如果基于第二条继续延伸一下,则有这样的可能性。有一种std::vector<bool>::reference的实现方式是让对象含有一个指针,指涉到一个机器字(word),该及其子持有的那个被引用的比特,在加上基于那个比特对应的字的偏移量。在这种实现框架下,我们继续探讨。

对features的调用会返回一个std::vector<bool>型别的临时对象,该对象没有名字,但是我们为讨论方便暂且称呼它为temp。针对temp执行operator[],返回一个std::vector<bool>::reference的对象,此对象含有一个指涉到机器字的指针,该机器字在一个持有temp所管理的那样比特的数据结构中,还要加上在第5个比特所对应的机器字偏移量。由于highPrioritytemp的一个副本,所以highPriority也含有一个指涉到temp中的机器字的指针说了那么多屁话,其实就是浅拷贝啦。但是在表达式结尾处temp析构了,因为它是个临时对象!这个时候要命的highPriority里就有悬空指针啦。

总结:auto在什么时候会出错

auto和 "隐形"代理类无法和平共处

代理类,就是为了模拟或增广其他型别的类。

  • 模拟:例如std::vector<bool>::reference就是为了制造std::vector<bool>operator[]返回了一个比特引用的假象。这个同样的例子在std::bitset对应的std::bitset::reference里也一样.

  • 增广: 表达式模板技术,提高数值计算代码的效率。

            Matrix sum = m1 + m2 + m3 + m4;
    

    这里Matrix对象的operator+返回的是结果的代理而不是结果本身,则上述计算会高效很多。

总体说来,我们需要避免如下代码:

auto someVar = "隐形"代理型别表达式;

如何发现“隐形”代理类

因为隐形代理类的设计就是为了隐藏细节,所以一般来说使用过程中设计者是尽可能让人不察觉到。这个时候一般只能从源码/说明文档里找到端倪。

  • 文档:使用的库往往会在文档中写明这一点。

  • 头文件:假设已经知道std::vector<T>operator[]返回值是T&,但是看到了返回值不是这样,而是定义了一个新的类型。那么应该是代理类。

对于“隐形”代理类的处理方式

即便发现有隐形代理类的时候,auto可能不太好用,但也可以用别的方式让auto继续起效。例如下述方式:

auto highPriority = static_cast<bool>(features(w)[5]);

这种方式必定能能够让代码稳妥的运行,也能使auto继续起效。

要点速记
1. “隐形”的代理型别可以导致auto根据初始化表达式推导出“错误的”型别。
2. 带显示型别的初始化习惯用法强制auto推导出你想要的型别。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值