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(或这种类型的数组)的非静态数据成员