使用 auto 关键字的对象类型推导

在下面的简单变量定义中,潜藏着一个细微的冗余:

double d{ 5.0 };

C++ 要求为所有对象显式指定类型,于是我们把变量 d 声明为 double。然而,用来初始化 d 的字面量 5.0 本身就具有 double 类型(其类型由字面量格式隐式决定)。

相关内容 字面量类型的确定方法见 5.2 课 —— 字面量。

当变量与其初始化式需要同一类型时,我们实际上把类型信息重复了两次。

已初始化变量的类型推导

类型推导(type deduction,又称类型推断)允许编译器根据初始化式自动推导对象类型。定义变量时,可用 auto 关键字代替显式类型:

int main()
{
    auto d { 5.0 };       // 5.0 是 double 字面量,d 被推导为 double
    auto i { 1 + 2 };     // 1 + 2 结果为 int,i 被推导为 int
    auto x { i };         // i 为 int,x 被推导为 int
    return 0;
}
  • 第一行:字面量 5.0double,故 d 类型为 double
  • 第二行:表达式 1 + 2 结果为 int,故 i 类型为 int
  • 第三行:因 i 已是 intx 同样为 int

警告 在 C++17 之前,auto d{ 5.0 }; 会把 d 推导为 std::initializer_list<double> 而非 double。C++17 已修复,GCC 与 Clang 亦将该修复反向移植到旧标准。若使用 C++14 或更早版本且编译失败,请改用拷贝初始化:

auto d = 5.0;

由于函数调用是合法表达式,当初始化式为非 void 函数调用时,同样可用类型推导:

int add(int x, int y) { return x + y; }

int main()
{
    auto sum { add(5, 6) };   // add 返回 int,sum 被推导为 int
    return 0;
}

字面量后缀可与类型推导联用,以指定特定类型:

auto a { 1.23f };   // f 后缀使 a 被推导为 float
auto b { 5u };      // u 后缀使 b 被推导为 unsigned int

带限定符的推导

使用 auto 的变量也可结合 constconstexpr 等限定符:

int a { 5 };

const auto b { 5 };      // b 为 const int
constexpr auto c { 5 };  // c 为 constexpr int

类型推导必须有推导源

类型推导不适用于无初始化式或空初始化式的对象,也不适用于初始化式类型为 void 或不完整类型的情况:

auto a;           // 错误:无法推导
auto b { };       // 错误:无法推导
auto c { foo() }; // 错误:foo() 返回 void

尽管对基本类型仅节省少许键击,但在后续课程中我们将遇到复杂且冗长的类型;此时 auto 能显著减少打字与笔误。

相关内容 指针与引用的类型推导规则更复杂,见 12.14 课 —— 指针、引用及 const 的类型推导。

类型推导会丢弃顶层 const

多数情况下,类型推导会去掉初始化式中的顶层 const

const int a { 5 };
auto b { a };   // b 为 int(const 被丢弃)

若需要保留 const,需显式添加:

const auto b { a };   // b 为 const int

字符串字面量的推导

因历史原因,字符串字面量类型特殊:

auto s { "Hello" };   // s 为 const char*,而非 std::string

若需推导为 std::stringstd::string_view,应使用字面量后缀 ssv

#include <string>
#include <string_view>

int main()
{
    using namespace std::literals;
    auto s1 { "goo"s  };   // s1 为 std::string
    auto s2 { "moo"sv };   // s2 为 std::string_view
}

不过,此时也可考虑不用类型推导。

类型推导与 constexpr

constexpr 并非类型系统的一部分,无法被推导;但 constexpr 变量隐式带 const,此 const 会被丢弃,可手动加回:

constexpr double a { 3.4 };
auto b { a };           // b 为 double
const auto c { a };     // c 为 const double
constexpr auto d { a }; // d 为 const double

类型推导的利弊

优点:

  1. 连续定义变量时名称对齐,提高可读性:
    auto c { 5 };
    auto d { 6.7 };
    
  2. 强制初始化,避免未初始化变量:
    auto y;   // 编译错误:无法推导
    
  3. 避免意外性能损失的转换:
    auto s { getString() };   // 若 getString() 返回 std::string_view,则无额外转换
    

缺点:

  1. 隐藏类型信息,IDE 虽可悬停查看,但仍易出错:
    auto y { 5 };   // 本意 double,却得 int
    
  2. 若初始化式类型改变,变量类型随之改变,可能产生意外行为:
    auto sum { add(5, 6) + gravity };   // add 或 gravity 类型变化,sum 也变化
    

现代共识:对对象使用类型推导通常是安全的,可淡化类型细节,突出逻辑。

最佳实践

  • 当对象类型无关紧要或显而易见时,使用 auto
  • 需要与初始化式不同类型,或类型对理解代码很重要时,显式写出类型。

作者注 后续课程在讲解概念或示例时,若显式类型有助于理解,将继续使用显式类型而非 auto

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"