avatar

Catalog
Effective Modern C++ 笔记(2):auto

Chapter 2 auto

Item 5 Prefer auto to explicit type declarations

在定义变量时,相较于显式地指定其类型,使用 auto 有以下优点:

  • 强制初始化
  • 避免用于初始化的表达式类型和声明的类型不一致,从而导致可移植性或执行效率的问题
  • 更改一处类型会自动更新和该类型有关的 auto,便于重构
  • 少打很多字

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

  • 在使用 auto 来定义变量时,如果用于初始化的表达式类型为 invisible proxy class,则类型推断的结果有可能和预想的不一致:

    c++
    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::vector<bool> features() {
    return std::vector<bool>(5, true);
    }

    cout << features()[3] << endl; // 1
    auto a = features()[3];
    cout << a << endl; // 0
    bool b = features()[3];
    cout << b << endl; // 1

    此处 std::vector<bool>::operator[] 的返回值类型是 std::vector<bool>::reference 而非 bool&,前者即为一种 invisible proxy class。

    所谓 proxy class,是为了模拟或强化别的类型而设计的。有的 proxy class 对用户来说很明显,例如智能指针;而有的则设计得很隐晦,试图对用户封装这一细节,这类 proxy class 就称作 invisible proxy class。std::vector<bool>::reference 就是其中之一,使用它是因为 std::vector 对于 bool 类型元素的存储方式是一个元素占一个 bit,而 C++ 不允许对 bit 的引用,那么 operator[] 自然无法返回 bool&,只好返回一个表现得像 bool&std::vector<bool>::reference

    正常情况下,用 std::vector<bool>::reference 初始化一个 bool 类型的变量,会触发一次隐式转型,将转型后的值赋给该变量。而初始化 auto 变量时,类型推断为 std::vector<bool>::reference,该变量的实际值依赖于具体实现细节,比如有可能是指向存储该 bit 的字节的指针以及一个字节内的偏移量,但是该函数的返回值是个右值,这条语句过后指针就悬空了,导致未定义的行为。

  • 对于如何判断函数的返回值是不是 invisible proxy class,没有什么太好的方法,书中给的建议是阅读文档和源代码。

    需要注意的一点是,这并不是 auto 的问题,只是用户预期的类型和 auto 推断出的类型不一致(auto 仍然遵守着它的规则),所以解决这一问题并不需要放弃 auto,而可以使用 explicitly typed initializer idiom,也就是将用于初始化的表达式的类型转换成用户想要的类型:

    c++
    1
    auto a = static_cast<bool>(features(w)[5]);

    此外,explicitly typed initializer idiom 并不是只有在遇到 invisible proxy class 才可以使用,当想要强调转型这一事实时也可以使用(毕竟 explicitly typed initializer idiom 本质上就是 static_cast

Reference

Author: Gusabary
Link: http://gusabary.cn/2020/05/21/Effective-Modern-C++-Notes/Effective-Modern-C++-Notes(2)-auto/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Comment