跳转至

指定初始化(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

解决什么问题

聚合类型过去只能按声明顺序逐个初始化:

ElfShdr<E> shdr = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0};

字段一多,位置和含义就对不上;想只设置其中一个字段,还要给前面的字段补零。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++ 的约束书写。