C++ 的一些特殊类类型

https://zh.cppreference.com/w/cpp/named_req/TrivialType

https://zh.cppreference.com/w/cpp/language/classes

https://www.bilibili.com/video/BV1Mz4y1M7hy

C++ 中有一些特殊的类类型,如平凡类型、标准布局类型、POD 类型、聚合体类型

首先,为什么会有这些类型:

  • 兼容 C ABI,通过某些限制可以与 C 进行交互

聚合体类型

数组类型是聚合体

符合以下条件的类是聚合体:

  • 没有用户声明或者提供的构造函数
  • 所有非静态数据成员(包括继承而来的,也就是说只能 public 继承)都是非静态的
  • 没有虚基类、虚函数

例如:

class MyString : public std::string {};

int main() {
    std::cout << std::is_aggregate_v<std::string> << std::endl;
    std::cout << std::is_aggregate_v<MyString> << std::endl;
    
    return 0;
}

两个都返回 true

在旧的 C++ 标准中,聚合类型不能有继承

聚合类型可以进行聚合初始化

聚合初始化

聚合初始化的语法即:

int arr[3] = {1, 2, 3};

struct S {
    int x;
    float y;
};

S s = {1, 1.2f};

对于数组来说,它按下标顺序包含所有数组元素

对于类来说,它按声明顺序包含所有数据成员(如果有基类,那么也是按照声明顺序)

并且,聚合初始化可以有附属关系,即:

S arrs[3] = {
    {1, 1.2f},
    {2, 2.2f},
    {3, 3.2f},
};

在新标准中,可以使用指派符来指定初始化哪个变量,如:

S ss = {.x = 1, .y = 1.2f};

平凡类型(Trivial Type)

平凡类型包括:

  • 标量类型:算术、枚举、指针、成员指针类型
  • 平凡类类型
  • 上面两种类型的数组

重点就是平凡类类型

平凡类类型

在一个类中,有以下一些特殊的成员函数:默认构造、拷贝构造、移动构造、拷贝赋值、移动赋值、析构

它们都有“平凡”的概念,如果一个特殊的成员函数是平凡的,那么必须满足:

  • 它必须是编译器自动生成的,并且不能被删除
  • 所在的类没有虚基类,没有虚函数,并且(如果是析构函数,那么)析构函数不能是虚的
  • 子对象(基类和非静态数据成员)的对应成员函数也都是平凡的

再来复习一下什么时候不会生成某些特殊成员函数:

  • 默认构造:子对象没有默认构造,某个成员是 const 或者引用且没有初始化
  • 移动构造和移动赋值:声明了拷贝构造、拷贝赋值或者析构,子对象没有对应的特殊成员函数
  • 拷贝构造和拷贝赋值:有右值引用成员(因为右值引用不能绑定左值),子对象没有对应的特殊成员函数
  • 析构:子对象没有析构

如果满足:

  • 所有的拷贝构造、移动构造、拷贝赋值、移动赋值、析构(除去默认构造)都是平凡的
  • 至少有一个拷贝构造、移动构造、拷贝赋值、移动赋值、析构

那么称该类为可平凡拷贝类

如果一个可平凡拷贝类,它:

  • 所有的默认构造都是平凡的
  • 至少有一个默认构造

那么称该类为平凡类

标准布局类型

一个类必须满足:

  • 没有非标准布局类型的非静态数据成员和基类
  • 没有虚函数、虚基类
  • 所有数据成员有相同的可访问性
  • 继承层级中仅有一个类具有非静态数据成员
  • 类中第一个非静态类型与基类不是同一个类型

这样它就是标准布局类型,它有一些作用:

POD(简旧数据类)

如果一个类是 POD,那么它必须:

  • 是平凡类型
  • 是标准布局类型
  • 没有非 POD(或这种类型的数组)的非静态数据成员