1. C++17 中的聚合类
聚合类就是一种可以直接用初始化列表 {} 进行初始化的类(像 struct 或数组一样)。它通常用来表现“数据集合”,没有太复杂的封装逻辑。
在 C++17 之前,一个类要成为聚合类,必须满足以下严苛条件:
- 没有用户声明的构造函数。
- 没有私有或保护的非静态数据成员(必须是 public 的)。
- 没有虚函数。
- 没有虚基类。
在 C++17 之前,如果想让一个类继承自另一个类,同时又想保持聚合初始化的特性(即直接用 {} 赋值),是做不到的。一旦继承了基类,这个类就不再是聚合类了,必须写构造函数来转发参数给基类。
C++17 放宽了限制,允许聚合类拥有基类(前提是基类本身也是非虚的聚合类)。这意味着我们可以更方便地扩展数据结构。现在的规则是:
- 只要基类也是非虚的、没有私有/保护成员的,就可以继承它。
- 派生类依然可以不写构造函数,直接使用
{} 初始化。
- 初始化顺序: 先初始化基类部分,再初始化派生类成员。
综上所述,在C++17中对聚合类的要求如下:
- 必须是公有继承(private 或 protected 继承都不行)
- 不能是虚继承
- 不能有用户提供的构造函数
- 不能有私有或受保护的非静态数据成员,并且所有非静态数据成员必须是公有的
- 不能有虚函数
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 { private: int num;
};
int main() { std::cout << std::boolalpha; std::cout << "Base is aggregate: " << std::is_aggregate<Base>::value << std::endl; std::cout << "Derived is aggregate: " << std::is_aggregate<Derived>::value << std::endl; std::cout << "NonDerived is aggregate: " << std::is_aggregate<NonDerived>::value << std::endl; std::cout << "NonDerived1 is aggregate: " << std::is_aggregate<NonDerived1>::value << std::endl; std::cout << "NonDerived2 is aggregate: " << std::is_aggregate<NonDerived2>::value << std::endl; 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++ 代码在保持高性能的同时,写起来更加轻量和直观。