Item 6: Use the explicitly typed initializer idiom when auto deduces undesired types.

Item 6: Use the explicitly typed initializer idiom when auto deduces undesired types.

这次是对 Effective Modern C++ Item 6 的学习笔记。

Item 5 中介绍了使用 auto 申明类型的优势,也在 Item 2 介绍了 auto 类型推导的方式和 auto 类型推导有时候并非如我们所愿的情况,本文继续分析使用 auto 存在的问题。

看下面的例子,函数 features 入参为 Widget 类型,返回一个 std::vector<bool>,每一个 bool 代表 Widget 是否提供一个特殊的特性:

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

假设 bit 5 代表是否有高优先级,可能编码如下:

Widget w;bool highPriority = features(w)[5];  // is w high priority?processWidget(w, highPriority);     // process w in accord
                                    // with its priority

如果修改 highPriority 显示申明为 auto

auto highPriority = features(w)[5];  // is w high priority?

将导致 processWidget 出现不可预测的行为,这是为什么呢?对 vector <T>operator [] 操作,一般我们期望得到 T& 类型。但是对于 vector <bool>operator [] 操作,得到的是 std::vector<bool>::reference 类型,却不是 bool& 类型。

为什么会有std::vector<bool>::reference 类型呢?主要是以下几个原因:

  1. 为了节省空间,使用 1 个 bit 代替 1 个字节的 bool 类型。
  2. std::vector<T>operator [] 操作应该返回的是 T&, 但标准库无法返对 bit 的引用。
  3. 为了得到接近 bool& 的类型,std::vector<bool>::reference 对象能够使用在 bool& 可以使用的场景。

由于以上几点,再看下这段代码:

bool highPriority = features(w)[5];  // is w high priority?

这里,features 返回的是 std::vector<bool> 对象,然后施加 operator [] 操作得到 std::vector<bool>::reference 对象, 最后被隐式转化为 bool 类型来初始化 highPriorityhighPriority 得到 std::vector<bool>bit 5 的值。

auto highPriority = features(w)[5]; // deduce highPriority's type

bool 换成 auto,由于 auto 的自动类型推导,最后无法得到 std::vector<bool>bit 5 的值。具体是什么值取决于 std::vector<bool>::reference 的实现。std::vector<bool>::reference 的一种实现是这样的:这个对象包含一个指针,这个指针指向一个 wordword 存储了引用的 bit ,加上 wordbitoffset。在这里,features 返回的是一个 std::vector<bool> 临时对象 tempoperator [] 操作后,得到 std::vector<bool>::reference ,它包含一个指向 tempword 的指针,加上 bit 5 的偏移。因此,在语句结尾,临时的 temp 被销毁,highPriority 包含了一个野指针,这将导致随后的 processWidget 函数的不可预测的行为。

这里可以使用显示类型初始化方式使用 auto

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

std::vector<bool>::reference 是代理类(proxy class)的一个例子,代理类的目的是为了模拟和扩展某些其他类型行为,具有广泛的使用,比如标准库的智能指针类型。

在表达式模板中也使用代理类的技术,能够提高数值计算的效率。比如给定一个类 MatrixMatrix 的对象 m1, m2, m3m4,以下表达式:

Matrix sum = m1 + m2 + m3 + m4;

operator + 返回一个代理类对象将更加高效。operator + 两个 Matrix 对象得到代理 Sum<Matrix, Matrix> 对象,而不是Matrix 对象,上面的表达式将得到 Sum<Sum<Sum<Matrix, Matrix>,Matrix>, Matrix> 对象,最后隐式转化为为 Matrix 对象。

这种隐形的代理类遇到 auto 时候往往得不到预期的结果。因此,一般防止出现以下语句:

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

一种方式是使用显式的类型申明,一种方式是使用 static_cast 显示类型初始化:

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

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

这样的习惯不仅用于代理类的情况,在定义一个变量的类型及其初始化表达式类型不同时,也最好显式申明变量类型,或者显示类型初始化。例如:

double calcEpsilon(); // return tolerance value

float ep = calcEpsilon(); // impliclitly convert double → float
auto ep = static_cast<float>(calcEpsilon());

最后,总结一下:

  • 隐式的代理类型可能导致 auto 类型推导结果不符合预期。
  • 对于这种代理类型一般使用显式类型申明或者显示类型初始化。
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值