条款6:当auto推导出意外的类型时,使用显式的类型初始化语义

条款5解释了使用auto来声明变量比使用精确的类型声明多了了很多的技术优势,但有的时候,当你想要zag的时候,auto可能会推导出了zig。例如,我有一个函数,它以const Widget&作为参数,并且返回std::vector<bool>,每一个bool暗示了Widget是否提供了一个特殊的特性。

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

进一步假设第5个bool暗示了Widget是否拥有比较高的优先级,我们可以写下这样的代码。

Widget w;

bool highPriority = features(w)[5]; // w是否有较高的优先级?

processWidget(w, highPriority); //根据w是否拥有较高的
//的优先级来对它进行处理

这段代码没有任何问题,它会很好的工作,但是如果我们声明highPriority时用看起来无害的auto代替精确的类型声明

auto highPriority = features(w)[5]; // w是否有较高的优先级?

在这种情况下,所有的代码都会编译成功,但是它的行为却是未定义的:

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

就像注释指出的那样,对processWidget的调用行为现在是未定义的了,但是为什么呢,答案可能会十分令人惊讶,在使用auto的代码中,highPriority的类型不再是bool,尽管std::vector<bool>概念上应该持有bool对象,但[]运算符并不返回容器内元素的引用(std::vector::operator[]返回容器的每一个类型除了bool),相反它返回一个std::vector<bool>:reference类型的对象(std::vector<bool>中的内部类)

 

std::vector<bool>::reference的存在是因为std::vector<bool>内部用一种紧缩的形式来表示bool对象,每一个bit代表一个bool对象,这使得std::vector<bool>的[]运算符出现了问题,因为std::vector<T>的[]运算符应该返回T&类型的对象,但是C++禁止返回对位对象的引用。无法返回bool&,std::vector<bool>的[]运算符返回了一个对象,它的行为看起来很像bool&,为了让这个想法能够成功,std::vector<bool>::reference对象必须能够在bool&都够使用的地方同样适用,在features中,std::vector<bool>::reference实现这个工作是通过一个到bool的隐式转换(不是bool&到bool,为了完整的解释std::vector<bool>::reference模拟bool&行为中使用的技术将会将我们带的太远太远,所以我简单的说这个隐私的转换只是很小的一部分(I’ll simply remark that this implicit conversion is only one stone in a larger mosaic)

带着这个思想,我们再来看一看最初的代码

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

这里,features返回了一个std::vector<bool>对象,并在这个对象上调用了[]运算符,[]运算符返回了一个std::vector<bool>::reference对象,这个对象为了初始化highPriority对象被隐式的转化为了一个bool对象。highPriority因此最终通过features获得了std::vector<bool>中第5个bit的值,就像它本应该的那样。

 

对比一下如果用auto声明highPriority会发生什么呢?

 

features返回了一个std::vector<bool>对象,[]运算符作用在了上面,[]继续返回一个std::vector<bool>::reference对象,但是现在有了一点改变,因为使用了auto来声明highPriority的类型,highPriority并不拥有features返回的std::vector<bool>对象的第5个bit的值。

 

highPriority的值取决于std::vector<bool>::reference是如何实现的,一种实现方式是std::vector<bool>::reference包含一个指针指向机器字,加上对引用位的偏移,我们考虑一下如果std::vector<bool>::reference是这样实现的,highPriority的初始化意味着什么。

 

对features的调用返回了一个临时的std::vector<bool>对象,这个对象没有名字,但是为了方便讨论,我这里叫它temp,[]运算符在temp上调用,返回的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是一个代理类的例子,一个类存在的目的是模拟和增强另一些类型的行为,代理类被应用于各种各样的目的,std::vector<bool>::reference的存在是提供一个std::vector<bool>::reference的[]运算符返回了一个对位的引用的错觉,标准库的智能指针类型(参见第4章)移植了裸指针的资源管理(the Standard Library’s smart pointer types (see Chapter 4) are proxy classes that graft resource management onto raw pointers. The)。代理类的特性已经被广泛的建立了,事实上在设计模式的宫殿中代理模式是存在时间最长的成员之一。

 

一些代理类对客户来说是很显然的,例如std::shared_ptr和std::unique_ptr,而另一些代理类被设计的或多或少有些不可见,例如std::vector<bool>::reference,std::bitset::reference。

 

同样C++中一些库库中的类使用了一种叫表达式模板的东西,这些库早先的目的是为了提高数字运算(numeric code)的效率,假定有一个Matrix类和4个Matrix对象,m1,m2,m3,m4。

Matrix sum=m1+m2+m3+m4

如果+运算符返回一个结果的代理而不是结果本身的话,运算会更有效率。两个Matrix对象的+可以返回一个代理类,例如Sum<Matrix,Matrix>而不是Matri对象本身。和std::vector<bool>::reference和bool的例子一样,代理类和Matrix之间会有一个隐私的转化,允许代理对象初始化等号右边的sum对象(初始化对象的表达式可能会是Sum<Sum<Sum<Matrix,Matrix>,Matrix>,Matrix>,这个类型肯定需要对客户隐藏起来)

照例,不可见的代理类和auto间相处的并不是很好,这些代理类通常被设计为不会存活超过一条语句,所以创建这样类型的变量违背了基础库的设计假设,就像std::vector<bool>::reference,我们可以看到违背这样假设会引发未定义的行为。

 

因此你会想要避免这样形式的代码:

auto someVar = expression of "invisible" proxy class type;

但是你应该如何识别代理类呢,使用到代理类的代码不太可能会突显出他们的存在,他们至少在概念上是不可见的,一旦你发现他们,难道你应该抛弃auto和条款5提到的auto带来的大量优点吗?

首先让我们看看你应该如何找到代理类,尽管代理类被设计为对程序员不可见的,但是使用到代理类的库提供的文档经常会标注出他们的存在,你对你使用的库越熟悉,你就越有可能发现这些代理的使用(The more you’ve familiarized yourself with the basic design decisions of the libraries you use, the less likely you are to be blindsided by proxy usage within those libraries.)

当文档比较短小的时候,头文件可以弥补这个缺陷,因为源代码几乎不可能完全的掩盖代理对象的存在,代理对象通常会从函数的调用中返回(They’re typically returned from functions that clients are expected to call),所以函数的原型反应了他们的存在,这里是std::vector<bool>::operator[]的函数原型

namespace std { // 从C++标准中
template <class Allocator>
class vector<bool, Allocator> {
public:

class reference { … };
reference operator[](size_type n);

};
}

假定你知道std::vector<T>的[]运算符应该返回一个T&对象,[]运算符意外的返回了其他类型的对象通常便会意味着代理类的存在,多关注你使用的函数接口能让你早些发现代理类的存在。

在实践中,很多的开发者只有当他们追踪神秘的编译问题或是调试不正确的单元测试结果时才会发现的代理类的存在。不管你是如何发现他们的,一旦auto被应用,推导出的类型将是代理类的类型而不是被代理的类型,解决的办法不是抛弃auto,auto本身不是问题,问题是auto推导出的类型并不是你想要的类型,解决办法是强制的让它推导出一个不同的类型,我把这个叫做显式的类型初始化语义(explicitly typed initializer idiom)

 

显式的类型初始化语义包括用auto声明一个变量,但是加上一个你想要auto推导出的初始化类型,下面是如何强迫将highPriority声明为一个bool类型

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

这里,features(w)[5]仍然返回一个std::vector<bool>::reference对象,就像之前一样,但是转换将表达式的类型变成了bool,接着auto将它的类型推导为highPriority了,在运行的时候,从std::vector<bool>::operator[]返回的std::vector<bool>::reference对象执行它支持的bool类型的转换,作为转换的一部分,从features返回的std::vector<bool>的指针被解引用(the still-valid pointer to the std::vector<bool> returned from features is dereferenced)。这避免了我们早先的未定义的行为,索引5接着被应用于相应的指针,最终产生bool类型来初始化highPriority。

对于Matrix这个例子,显式的类型初始化语义将会像这样:

auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);

 

这个应用并没有局限于会产生代理类的初始化,它同时也适用当你想强调你创造的变量的类型不同于初始化的表达式的时候,例如假如你有一个计算公差值的函数

double calcEpsilon(); // 返回公差值

calcEpsilon返回的类型是double,但是假定你知道对于你的应用float的精度就已经足够了,你更关心float和double在大小上的不同,所以你声明了一个float变量来储存calcEpsilon的结果。

float ep = calcEpsilon(); // 隐式的
// 将double转换为float

但是这个并没有说明我有意的改变了函数返回的类型,而使用显式的类型初始化语义可以:

auto ep = static_cast<float>(calcEpsilon());

 

如果你拥有一个float类型的表达式,但是你把它储存为一个整型的变量,也可以使用这个方法,假定你有一个带有随机访问迭代器(e.g., a std::vector, std::deque,or std::array)的容器,和一个在0-1之间的double类型来暗示元素离容器的开始有多远(0.5暗示了在容器的中间),最终的目的是计算获得这个元素的下标,如果你确定最终的结果不会超过int的范围,如果容器是c,double是d,你可以这样计算下标:

int index = d * c.size();

但是这并没有很好的体现出你有意的将右端的double转换为int,显式的类型初始化语义会让事情变的更加透明

auto index = static_cast<int>(d * c.size());

 

请记住

  • 不可见的代理类会导致auto从初始化表达式中推导出“错误”的类型。
  • 显式的类型初始化语义会迫使auto推导出你想要的类型。

Effective Modern C++翻译(7)-条款6:当auto推导出意外的类型时,使用显式的类型初始化语义的更多相关文章

  1. [Effective Modern C++] Item 6. Use the explicitly typed initializer idiom when auto deduces undesired types - 当推断意外类型时使用显式的类型初始化语句

    条款6 当推断意外类型时使用显式的类型初始化语句 基础知识 当使用std::vector<bool>的时候,类型推断会出现问题: std::vector<bool> featu ...

  2. Effective Modern C++翻译(6)-条款5:auto比显示的类型声明要更好

        在概念上说,auto关键字和它看起来一样简单,但是事实上,它要更微妙一些的.使用auto会让你在声明变量时省略掉类型,同时也会防止了手动类型声明带来的正确性和性能上的困扰:虽然按照语言预先定义 ...

  3. Effective Modern C++翻译(4)-条款3:了解decltype

    条款3 了解decltype decltype是一个有趣的东西,给它一个变量名或是一个表达式,decltype会告诉你这个变量名或是这个表达式的类型,通常,告诉你的结果和你预测的是一样的,但是偶尔的结 ...

  4. Effective Modern C++翻译(3)-条款2:明白auto类型推导

    条款2 明白auto类型推导 如果你已经读完了条款1中有关模板类型推导的内容,那么你几乎已经知道了所有关于auto类型推导的事情,因为除了一个古怪的例外,auto的类型推导规则和模板的类型推导规则是一 ...

  5. Effective Modern C++翻译(2)-条款1:明白模板类型推导

    第一章 类型推导 C++98有一套单一的类型推导的规则:用来推导函数模板,C++11轻微的修改了这些规则并且增加了两个,一个用于auto,一个用于decltype,接着C++14扩展了auto和dec ...

  6. Effective Modern C++翻译(5)-条款4:了解如何观察推导出的类型

    条款4:了解如何观察推导出的类型 那些想要知道编译器推导出的类型的人通常分为两种,第一种是实用主义者,他们的动力通常来自于软件产生的问题(例如他们还在调试解决中),他们利用编译器进行寻找,并相信这个能 ...

  7. Effective Modern C++翻译(1):序言

    /*********************************************************** 关于书: 书是我从网上找到的effective Modern C++的样章,内 ...

  8. [C++11] Effective Modern C++ 读书笔记

    本文记录了我读Effective Modern C++时自己的一些理解和心得. item1:模板类型推导 1)reference属性不能通过传值参数传入模板函数.这就意味着如果模板函数需要一个refe ...

  9. 《Effective Modern C++》翻译--条款2: 理解auto自己主动类型推导

    条款2: 理解auto自己主动类型推导 假设你已经读过条款1关于模板类型推导的内容,那么你差点儿已经知道了关于auto类型推导的所有. 至于为什么auto类型推导就是模板类型推导仅仅有一个地方感到好奇 ...

随机推荐

  1. noaman日志第一条:2015-1024;“Hello.World”

    在南京的不知道第几个周末,一夜的煎熬终于活过来了.清早起来开通了自己的博客,第一条说说就记录开通博客这个事件.没有别的. 之后我会着重记录每天看书内容,以及所要编写的重要程序,一点一滴地积累希望能收获 ...

  2. String、StringBuffer、StringBuilder之间的区别

    String                      字符串常量 StringBuffer         字符串变量(线程安全) StringBuilder       字符串变量(非线程安全) ...

  3. VB中判断空的几种方法,Null, Missing, Empty, Nothing, vbNullString区别

    vb6中存在几个虚幻的值:Null.Missing.Empty.Nothing.vbNullString.除了最后一个之外,每一个值都不能直接用“a=值”来判断.下面分别解释一下这几个值的含义. 1. ...

  4. SSH登录很慢问题的解决

    用ssh连其他linux机器,会等待10-30秒才有提示输入密码.严重影响工作效率.登录很慢,登录上去后速度正常,这种情况主要有两种可能的原因: 1. DNS反向解析的问题 OpenSSH在用户登录的 ...

  5. DBA_Oracle Erp R12安装虚拟机镜像IP修正(案例)

    2014-07-12 Created By BaoXinjian

  6. IGS_学习笔记06_IREP发布客户化集成接口为Web Service(案例)

    2015-01-03 Created By BaoXinjian

  7. Codeforces Round #368 (Div. 2) D. Persistent Bookcase

    Persistent Bookcase Problem Description: Recently in school Alina has learned what are the persisten ...

  8. BestCoder Round #86 部分题解

    Price List 题意: 有n件商品,每天只能买一件,并且会记录账本,问有多少次一定记多了? 题解: 就是求和,最后如果大于和就输出1,否则0. 代码: #include <bits/std ...

  9. JAVA 创建类,使用类

    一.创建类: Test.java //定义类 public class Test{ //属性 String name; String gender; int age; //方法,无参无返回 publi ...

  10. MYSQL批量插入数据库实现语句性能分析

    假定我们的表结构如下 代码如下   CREATE TABLE example ( example_id INT NOT NULL, name VARCHAR( 50 ) NOT NULL, value ...