带有基类的聚合类

1. C++17 中的聚合类

聚合类就是一种可以直接用初始化列表 {} 进行初始化的类(像 struct 或数组一样)。它通常用来表现“数据集合”,没有太复杂的封装逻辑。

在 C++17 之前,一个类要成为聚合类,必须满足以下严苛条件:

  1. 没有用户声明的构造函数
  2. 没有私有保护的非静态数据成员(必须是 public 的)。
  3. 没有虚函数
  4. 没有虚基类

在 C++17 之前,如果想让一个类继承自另一个类,同时又想保持聚合初始化的特性(即直接用 {} 赋值),是做不到的。一旦继承了基类,这个类就不再是聚合类了,必须写构造函数来转发参数给基类。

C++17 放宽了限制,允许聚合类拥有基类(前提是基类本身也是非虚的聚合类)。这意味着我们可以更方便地扩展数据结构。现在的规则是:

  1. 只要基类也是非虚的、没有私有/保护成员的,就可以继承它。
  2. 派生类依然可以不写构造函数,直接使用 {} 初始化。
  3. 初始化顺序: 先初始化基类部分,再初始化派生类成员。

综上所述,在C++17中对聚合类的要求如下:

  1. 必须是公有继承(private 或 protected 继承都不行)
  2. 不能是虚继承
  3. 不能有用户提供的构造函数
  4. 不能有私有或受保护的非静态数据成员,并且所有非静态数据成员必须是公有的
  5. 不能有虚函数

C++17 引入了关于复合类的类型萃取 std::is_aggregate,可以在编译时检查一个类型是否为聚合类。这对于写模板代码非常有用。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <type_traits>

struct Base
{
int x;
};
struct Derived : public Base
{
static int y;
};
struct NonDerived
{
int a;
NonDerived() {}
};

struct NonDerived1 : virtual public Base
{
int y;
};

struct NonDerived2 : public Base
{
//virtual void print(){}
private:
int num;

};

int main()
{
std::cout << std::boolalpha;
std::cout << "Base is aggregate: " << std::is_aggregate<Base>::value << std::endl; // true
std::cout << "Derived is aggregate: " << std::is_aggregate<Derived>::value << std::endl; // true (C++17)
std::cout << "NonDerived is aggregate: " << std::is_aggregate<NonDerived>::value << std::endl; // false
std::cout << "NonDerived1 is aggregate: " << std::is_aggregate<NonDerived1>::value << std::endl; // false
std::cout << "NonDerived2 is aggregate: " << std::is_aggregate<NonDerived2>::value << std::endl; // false

return 0;
}

2. 使用示例

2.1 聚合类初始化

在使用{}初始化聚合类对象的时候,初始化顺序为先初始化基类部分,再初始化派生类成员。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>
#include <vector>

// 简单的基类
struct Person
{
std::string name;
int age;
};

// 派生聚合类
struct Employee : Person
{
std::string department;
double salary;
int employee_id;
};

// 演示聚合初始化和使用
void demo_basic_aggregate()
{
std::cout << "=== 基本聚合类示例 ===" << std::endl;

// 聚合初始化
Employee emp{"张三", 30, "技术部", 15000.0, 1001};

// 访问成员
std::cout << "姓名: " << emp.name << std::endl;
std::cout << "年龄: " << emp.age << std::endl;
std::cout << "部门: " << emp.department << std::endl;
std::cout << "薪资: " << emp.salary << std::endl;
std::cout << "员工ID: " << emp.employee_id << std::endl;

std::cout << std::endl;
}

int main()
{
demo_basic_aggregate();
return 0;
}

2.2 结构化绑定

虽然在结构化绑定中直接解包带有基类的聚合比较少见,但聚合初始化为结构化绑定提供了底层支持。注意:结构化绑定不能直接用于有继承情况的聚合类,此时需要手动解构。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>

// 简单的聚合类
struct RGBColor
{
int red;
int green;
int blue;
};

// 带有基类的聚合类
struct NamedColor : RGBColor
{
std::string name;
};

void demo_structured_binding()
{
std::cout << "=== 聚合类与结构化绑定 ===" << std::endl;

// 基本聚合类的结构化绑定
RGBColor color{255, 128, 64};
auto [r, g, b] = color;
std::cout << "RGB颜色: (" << r << ", " << g << ", " << b << ")" << std::endl;

// 带有基类的聚合类
NamedColor namedColor{{200, 100, 50}, "橙红色"};

// 手动解构派生类
std::cout << "命名颜色: " << namedColor.name
<< " RGB: (" << namedColor.red << ", "
<< namedColor.green << ", " << namedColor.blue << ")" << std::endl;

std::cout << std::endl;
}

int main()
{
demo_structured_binding();
return 0;
}

C++17 允许聚合类拥有基类,这是一个小但美的特性。它消除了继承和 POD(纯旧数据)类型之间的隔阂,让 C++ 代码在保持高性能的同时,写起来更加轻量和直观。