指定初始化(designated initializers)¶
| 标准 | 难度 | 状态 | 分级 | 证据 | 审阅 | 最后校验 |
|---|---|---|---|---|---|---|
| C++20 | 2/5 | final | A | 3 条(manual 3) | 单审(zyuhao7) | 2026-10-26 |
编译器支持:GCC 8+ · Clang 10+ · MSVC 19.21+。版本查询:cppreference 编译器支持。
主张与证据¶
| 主张 | 支持证据 |
|---|---|
| 用指定初始化只设置聚合结构体的一个字段,其余字段保持值初始化 | ev-mold-designated-init-shdr-001 |
| 在构造函数初始化列表中用指定初始化构造基类聚合,只填必要的回调字段 | ev-duckdb-designated-init-engine-002 |
| 返回聚合时用指定初始化只填两个回调字段 | ev-duckdb-designated-init-iterator-003 |
本站证据页:ev-duckdb-designated-init-engine-002 · ev-duckdb-designated-init-iterator-003 · ev-mold-designated-init-shdr-001
解决什么问题¶
聚合类型过去只能按声明顺序逐个初始化:
字段一多,位置和含义就对不上;想只设置其中一个字段,还要给前面的字段补零。C++20 的指定初始化允许用 .field = value 只写关心的成员,其余成员按默认规则值初始化。
它和 C99 的指定初始化相似,但规则更严格:字段必须按声明顺序出现,不能嵌套指定,也不能和按位置的初始化混用。
基础用法¶
#include <iostream>
struct Point {
int x;
int y;
};
struct Segment {
Point from;
Point to;
bool visible;
};
int main() {
Point origin{};
Point p{.x = 1, .y = 2};
Segment s{.from = p, .to = origin, .visible = true};
std::cout << s.from.x << ' ' << s.to.y << ' ' << s.visible << '\n';
}
Point p{.x = 1, .y = 2} 按声明顺序设置两个成员;Segment s{...} 只写三个字段,嵌套的 Point 作为整体传入。未写出的成员会被值初始化,而不是保持不确定值。
进阶用法¶
指定初始化可以和默认成员初始化器、嵌套聚合以及 new 表达式组合。省略的字段按默认成员初始化器或值初始化规则处理。
struct Point {
int x = 1;
int y = 2;
};
Segment s{.from = {.x = 3}, .to = p};
auto q = new Point{.x = 4};
Segment s 里 .from = {.x = 3} 用一个新的指定初始化列表初始化嵌套聚合,这是允许的;但 .from.x = 3 这种嵌套指示符在 C++ 中不允许,必须用花括号把内层包起来。new Point{.x = 4} 说明指定初始化也适用于动态分配。
省略 Point::y 时,它使用默认成员初始化器 2;如果没有默认成员初始化器,则按值初始化得到 0。这一点和“保留旧值”不同,修改字段集合时要重新检查所有依赖默认值的路径。
完整示例见 examples/cpp20/designated_initializers.cpp。
真实项目证据¶
| 项目 | 位置 | 用法 | 为什么典型 |
|---|---|---|---|
| mold | src/mold.h#L403-L405 |
只设置 ELF 节头的 sh_addralign 字段 |
节头字段多且大多有默认值,指定初始化让“只关心对齐”的意图直接可见 |
| duckdb | extension/delta/src/delta_utils.cpp#L174-L176 |
在构造函数初始化列表中指定 C 回调结构体的两个函数指针 | C 接口结构体字段多,按位置写容易错位;指定字段能避免顺序猜测 |
| duckdb | extension/delta/src/delta_utils.cpp#L190-L195 |
返回迭代器聚合时只填 data 与 get_next |
返回位置同样需要表达“其余字段用默认值”,指定初始化保持意图清晰 |
三条证据均为 manual 级:作者复核,尚未引入第二名审核者。mold 覆盖聚合成员声明,DuckDB 的两处分别覆盖构造函数初始化列表与返回语句。
陷阱与边界¶
字段必须按声明顺序出现。 P p{.y = 2, .x = 1} 是编译错误;指定初始化不是任意顺序的命名参数。需要乱序赋值时,先默认构造再逐字段赋值。
不能和按位置初始化混用。 P p{.x = 1, 2} 不合法;同一个初始化列表要么全部指定字段,要么全部按位置。需要部分指定时,把剩余字段也写成指定形式,或使用构造函数。
C++20 不支持嵌套指示符。 .from.x = 1 这种写法在 C 中可能可用,但 C++ 只接受单个 .member。嵌套聚合要先用内层类型的值构造,再整体赋给外层字段。
只适用于聚合类型。 有用户提供的构造函数、私有非静态成员、虚函数或私有/受保护基类的类型不是聚合,不能用指定初始化;这类类型应通过构造函数表达字段语义。C++17 起,公开基类不再阻止一个类型成为聚合。
数组下标不能用指定初始化。 C++20 没有 [index] = value 形式的数组指定初始化;需要按下标设置时,先值初始化整个数组再逐个赋值。
省略的字段会被值初始化。 省略字段不是“保持原值”;对内置类型会变成 0,对类类型会调用默认构造函数。依赖未写出字段保留旧值时,会得到意料之外的结果。
C 兼容性有边界。 同样的结构体在 C 和 C++ 中可能都支持指定初始化,但 C 允许的顺序和嵌套规则更宽;跨语言头文件不要假设两边语法完全一致。
库联动¶
- mold:ELF 节头只设置对齐字段,其余字段依赖值初始化;字段多的聚合类型用指定初始化可以直接看出关心的部分。
- DuckDB:Delta 扩展的 C 回调结构与迭代器都在构造或返回时指定字段,避免按位置猜测字段含义。
- 与构造函数的分工:指定初始化只适用于聚合;需要校验、默认逻辑或多步初始化时,应写构造函数,不能为了少写字段而破坏类型的不变式。
横向对比¶
| 方案 | 字段顺序要求 | 可省略字段 | 适用类型 | 主要代价 |
|---|---|---|---|---|
| 按位置初始化 | 严格 | 只能从尾部省略 | 聚合类型 | 字段多时易错位,意图不清晰 |
| 指定初始化 | 严格按声明序 | 是 | 聚合类型 | C++20 起可用;不能嵌套、不能乱序 |
| 构造函数 | 由作者定义 | 由默认参数决定 | 任意类 | 需要写构造逻辑,聚合类型也要放弃聚合性质 |
| 默认构造后逐字段赋值 | 无 | 是 | 任意可赋值类型 | 代码更长;非平凡类型可能多一次构造/赋值 |
跨标准演进:C++20 通过 P0329R4 引入指定初始化,语法取自 C99 但收紧了顺序、嵌套和混用规则。截至 C++23,这些限制没有放宽,跨语言代码仍要按 C++ 的约束书写。