std::is_aggregate
什么是聚合体
聚合体(aggregate)是可以直接用花括号按成员顺序逐个初始化、而不经过任何构造函数的类型。 它包括两类:
- 数组:任何数组都是聚合体。
- 满足以下全部条件的类(struct、class、union):
- 没有用户声明的构造函数(C++20 的规则,各版本的差异见下表)
- 没有 private 或 protected 的直接非静态数据成员
- 没有虚函数
- 没有虚基类,也没有 private 或 protected 基类(C++17 起允许 public 非虚基类)
std::is_aggregate<T> 是 C++17 引入的类型萃取,定义在 <type_traits> 中,用于在编译期判断一个类型是不是聚合体。
; // 是
; // 不是:有 private 成员
; // 不是:有构造函数
; // C++17 起是:public 非虚基类
; // 不是:有虚函数
; // C++11–17 是,C++20 起不是
static_assert;
static_assert;
static_assert;
static_assert;
static_assert; // 标量类型不是聚合体
static_assert; // std::array 是
static_assert; // pair 有构造函数,不是
聚合初始化
A a; // 按成员的声明顺序初始化
A b; // 没给值的成员:有默认成员初始化器就用它,否则用 {} 初始化,y 为 0.0
A c; // 全部成员都用 {} 初始化,值为零
A d; // 默认初始化:int 和 double 的值不确定
D e; // C++17:先初始化基类,再初始化自己的成员
A f; // C++20 指定初始化器
A ; // C++20 用圆括号做聚合初始化
各版本的定义变化(面试常考)
| 版本 | 变化 |
|---|---|
| C++98/03 | 不能有用户声明的构造函数,不能有基类,不能有 private/protected 成员,不能有虚函数 |
| C++11 | 放宽为"不能有用户提供的构造函数",所以 = default 的构造函数仍算聚合体;但不能有默认成员初始化器(int x = 0;) |
| C++14 | 允许默认成员初始化器 |
| C++17 | 允许 public 非虚基类;不能有 explicit 构造函数和继承来的构造函数 |
| C++20 | 收紧为"不能有用户声明的构造函数",写成 = default 或 = delete 也不算聚合体了;新增指定初始化器、圆括号聚合初始化 |
C++20 收紧这条规则,是为了堵住一个漏洞:
;
NoCtor n; // C++17 能编译通过:它仍是聚合体,花括号初始化绕过了被删除的构造函数
// C++20 起编译错误
聚合体和 POD、平凡类型、标准布局的区别
这几个概念分别描述不同的方面,不是包含关系:
| 概念 | 关心什么 | 例子 |
|---|---|---|
| 聚合体 | 初始化的语法:能不能用花括号逐成员初始化 | struct { std::string s; int x; } 是聚合体,但不是平凡类型 |
| 平凡(trivial) | 特殊成员函数是不是平凡的,能不能直接 memcpy |
struct { private: int x; } 是平凡类型,但不是聚合体 |
| 标准布局 | 内存布局能不能和 C 语言兼容 | — |
| POD | 平凡类型并且是标准布局 | std::is_pod 在 C++20 中已被弃用 |
is_aggregate 用在哪里
1. 泛型代码里决定用哪种括号构造对象
C++20 之前,std::make_unique<P>(1, 2) 内部写的是 new P(1, 2),对聚合体编译不过,库作者只能自己分情况处理:
T
C++20 支持用圆括号做聚合初始化之后,make_unique、emplace_back 可以直接用于聚合体,这种写法就用得少了。
2. 无宏反射库
Boost.PFR 这类库只对聚合体有效:它们用"能用几个任意类型的实参做花括号初始化"来推算出结构体有几个字段,再结合结构化绑定取出每个字段,从而实现结构体的遍历、比较和序列化,不需要写宏。
常见坑
-
升级到 C++20 后编译失败:
struct S { S() = default; int x; }; S s{1};在 C++17 能编译,在 C++20 不行,因为 S 不再是聚合体。 -
升级到 C++17 后访问权限出错:
; ; D d; // C++14 OK:调用 D 隐式生成的构造函数,它能访问 B 的 protected 构造函数 // C++17 报错:D 变成了聚合体,基类 B 要在外部上下文里初始化,访问不了 protected 构造函数 -
花括号初始化禁止窄化转换:
A a{1.5, 2.0};报错,因为double转int是窄化。C++20 的圆括号聚合初始化允许窄化转换。 -
圆括号聚合初始化不延长临时对象的生命周期:成员是引用、并且绑定到一个临时对象时,用花括号初始化会延长这个临时对象的生命周期,用圆括号初始化不会,引用会悬空。
-
指定初始化器的限制(C++20):
- 必须按成员的声明顺序写
- 不能和按位置写的初始值混用
- 不能嵌套写成
.a.b = 1 - 不能用于数组
这些都比 C 语言严格。
-
std::array的双层花括号:std::array<int, 3>是一个内部包着一个 C 数组的聚合体。多数情况下可以省略内层的花括号,但元素本身也是聚合体时,比如std::array<std::pair<int, int>, 2>,有时必须写成两层花括号。 -
A a;不会把成员清零:没有默认成员初始化器的标量成员,值是不确定的。要写A a{};。
面试时可以这样回答:聚合体是没有用户声明的构造函数、没有 private/protected 成员、没有虚函数、只有 public 非虚基类的类,以及所有数组。它们可以直接用花括号按成员顺序初始化。定义随版本变化过:C++17 允许 public 基类,C++20 规定写了 = default 构造函数的类也不算聚合体,并加入了指定初始化器和圆括号初始化。
暂无评论,欢迎留下第一条评论。