C++14 中聚合类成员初始化

1. 聚合类

在 C++ 中,聚合类 是一种特殊的类类型,它主要用于表示数据的集合,类似于 C 语言的结构体。聚合类的定义非常严格,在C++11 及以后标准中,判定一个类型是否为聚合类通常遵循以下规则:

  1. 无用户自定义的构造函数:不能有用户显式提供的构造函数。
  2. 非私有/保护的非静态数据成员:所有的非静态数据成员必须是 public 的。
  3. 无虚函数:不能有虚函数。
  4. 无虚、私有或保护的基类:不能有虚基类,且基类必须是 public 的。

满足上述条件的类、结构体或数组,被称为聚合类。聚合类可以使用花括号初始化列表进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

struct Point // 这是一个普通的聚合类
{
int x;
int y;
};

int main()
{
// 数组作为聚合的演示
int arr[3] = {10, 20, 30}; // 合法的聚合初始化

// 普通聚合类的演示
Point p = {1, 2}; // 合法的聚合初始化

// 数组的数组(二维数组)也是聚合
int matrix[2][2] = {{1, 2}, {3, 4}};

return 0;
}

特别强调:数组是聚合类。 它具备聚合类的核心特征:没有用户定义的构造函数,支持使用花括号 {} 进行聚合初始化。

2. C++14 对聚合类的改进

C++14 对聚合类的一个重要改进是:允许在聚合类的非静态数据成员声明时直接提供默认成员初始化器

C++11 标准中规定:如果一个类或结构体中有任何非静态数据成员使用了 = brace-init(即在类定义内部,直接为非静态成员变量使用 {} 进行赋值) 或者使用 = value 形式进行初始化,那么它就不再是聚合类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct A
{
int x = 10; // 使用了 = value
int y = 20; // 使用了 = value
}; // C++11标准中,不再是聚合类

struct B
{
int x = {10}; // 使用了 = brace-init
int y = {20}; // 使用了 = brace-init
}; // C++11标准中,不再是聚合类

int main()
{
// 创建对象并初始化
A a = {10, 20}; // 错误,无法使用聚合初始化语法初始化对象!
B b = {10, 20}; // 错误,无法使用聚合初始化语法初始化对象!
return 0;
}

如果你使用 g++clang++ 编译上述代码,请务必指定 -std=c++11

1
g++ -std=c++11 test.cpp -o test

在终端会看到如下错误信息:

1
2
error: could not convert ‘{10, 20}’ from ‘<brace-enclosed initializer list>’ to ‘A’
error: could not convert ‘{10, 20}’ from ‘<brace-enclosed initializer list>’ to ‘B’

C++14 中,这个限制被放宽了。现在,即使成员拥有默认的初始化器,该类依然被视为聚合类。这带来了以下优势:

  1. 混合初始化模式:开发者可以为成员提供默认值,防止未初始化的情况,同时依然可以使用聚合初始化语法来覆盖这些默认值。
  2. 简化代码:如果仅仅是为了给成员设置初始值,不再需要编写构造函数。
  3. 向后兼容性增强:老旧的 C 风格结构体可以安全地添加默认值而不破坏现有的聚合初始化代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>

// 定义一个聚合类 (C++14 标准)
struct Point
{
// C++14 允许在这里直接初始化成员
// 如果在初始化列表中未提供该值,将使用此默认值
int x = 10;
int y = 20;
std::string name = "Origin";
};

int main()
{
Point p1;
std::cout << "p1: " << p1.x << ", " << p1.y << ", " << p1.name << std::endl;

Point p2{5};
std::cout << "p2: " << p2.x << ", " << p2.y << ", " << p2.name << std::endl;

Point p3{100, 200, "Target"};
std::cout << "p3: " << p3.x << ", " << p3.y << ", " << p3.name << std::endl;

return 0;
}

在测试程序中一共创建了三个对象:p1p2p3

  • Point p1;:全部使用默认值,p1.x = 10, p1.y = 20, p1.name = "Origin"
  • Point p2{5};:使用聚合初始化覆盖部分值 (C++14 特性),这遵循从左到右的赋值规则
    • p2.x = 5, p2.y 使用默认值 20, p2.name 使用默认值 "Origin"
  • Point p3{100, 200, "Target"};:覆盖所有值