每周技巧 #172:指定初始化器

本节阅读量:

本文翻译自 Abseil 官网的 Tip of the Week #172: Designated Initializers

原文最初作为 TotW #172 发布于 2019 年 12 月 11 日。

作者:Aaron Jacobs

更新于 2020 年 4 月 6 日。

快捷链接:abseil.io/tips/172

指定初始化器是 C++20 标准中的一种语法,用于以紧凑、可读、可维护的方式指定结构体内容。相比下面这种重复写法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct Point {
  double x;
  double y;
  double z;
};

Point point;
point.x = 3.0;
point.y = 4.0;
point.z = 5.0;

可以用指定初始化器写成:

1
2
3
4
5
Point point = {
    .x = 3.0,
    .y = 4.0,
    .z = 5.0,
};

这稍微减少了重复,但更重要的是,它可以用于更多上下文。例如,它意味着结构体可以被设为 const,而不必使用别扭的变通写法:

1
2
// 向读者表明,在这段可能更复杂的代码中,这个结构体永远不会变化。
const Point character_position = { .x = 3.0 };

它也可以直接用于函数调用,而不需要向作用域中引入额外标识符:

1
2
3
4
std::vector<Point> points;
[...]
points.push_back(Point{.x = 3.0, .y = 3.0});
points.push_back(Point{.x = 4.0, .y = 4.0});

语义

指定初始化器是聚合初始化的一种形式,因此只能用于聚合类型。这大致意味着“没有用户提供的构造函数或虚函数的 struct 或 class”,而在典型 Google 风格中,这大致也是我们会使用 struct 而不是 class 的场景。

C++20 指定初始化器的语义和你基于其他 C++ 特性(比如构造函数的成员初始化列表)所预期的差不多。显式提到的字段会按照顺序用给定表达式初始化;对于你希望采用“默认”行为的字段,可以省略:

1
2
3
4
5
Point point = {
    .x = 1.0,
    // y 将是 0.0
    .z = 2.0,
};

上面的“默认”是什么意思?除 union 等特殊情况外,答案是:

  • 如果结构体定义中包含默认成员初始化器(例如字段定义形如 std::string foo = "default value";),则使用它。
  • 否则该字段会像使用 = {} 一样初始化。实践中,这意味着普通数据类型会得到零值,更复杂的类会得到一个默认构造的实例。

这通常是最不令人意外的行为。细节可参见标准

一点历史和语言冷知识

指定初始化器自 C99 起就是 C 语言标准的一部分,并且在那之前编译器也已经把它作为非标准扩展提供。但直到最近它都不是 C++ 的一部分:这是 C 不是 C++ 子集的一个显著例子。因此,Google 风格指南过去说过不要使用它们

二十年后,情况终于改变了:指定初始化器现在已经是 C++20 标准的一部分

与 C 版本相比,C++20 形式的指定初始化器有一些限制:

  • C++20 要求字段在指定器中的列出顺序与它们在结构体定义中的顺序一致(所以 Point{.y = 1.0, .x = 2.0} 不合法)。C 没有这个要求。
  • C 允许混合使用指定和非指定初始化器(Point{1.0, .z = 2.0}),但 C++20 不允许。
  • C 支持一种用于稀疏初始化数组的语法,称为“数组指定器”。这不是 C++20 的一部分。

每周技巧 #171:避免哨兵值

上一节

每周技巧 #173:用选项结构体包装参数

下一节