相关文章推荐
跑龙套的红烧肉  ·  Rational Design of ...·  1 年前    · 
不拘小节的砖头  ·  CentOS7 ...·  2 年前    · 
冲动的八宝粥  ·  Android 12 ...·  2 年前    · 
酒量小的充值卡  ·  DataRow ...·  2 年前    · 

整理一下 C++ POD 类型的细节(一)

POD type(简旧数据类型)是 C++ 标准中对于对象类型的一些要求。

下篇: 整理一下 C++ POD 类型的细节(二)


内容引用自 n4659 (C++17 工作草案)和 n4861 (C++20 工作草案),尽量不讨论古代 C++ 。


名称

POD 类型中的 POD 指的是 plain old data ,简旧数据。

POD 本意可能是直接与 C 库二进制交流的类型,但其中也有 C++ 独有的类型。


POD 类型的要求

POD 类型是以下类型的统称(n4659 6.9,9):

  1. 标量类型(scalar type)
  2. POD 类
  3. 以上类型的数组和 cv 限定版本

标量类型是以下类型的统称:

  1. 算术(整数、浮点)类型
  2. 枚举类型
  3. 指针类型
  4. 指向成员指针类型
  5. std::nullptr_t 类型
  6. 以上类型 cv 限定版本

(以下的类含 class/struct 和 union)

POD 类是以下类型的统称(n4659 12,10):

  1. POD struct
  2. POD union

POD struct 和 POD union 的要求:

  • 平凡类(trivial class)
  • 标准布局类(standard-layout class)
  • 无非 POD 类类型或其数组的非静态数据成员
  • 两者区别是一个是非联合类(class/struct),另一个是 union


标准布局类的要求

标准布局类要求(n4659 12,7):

  • 无非标准布局类型的基类
  • 无虚函数或虚基类
  • 对于每个直接或间接基类,只有一个该基类的子对象
  • 无非标准布局类型(或其数组)或引用类型的非静态数据成员
  • 所有非静态数据成员拥有同一访问控制(public/protected/private)
  • 该类和基类中的所有非静态数据成员、位域都声明于同一类中
  • 基类的类型都与该类“零偏移成员类型集”中成员类型不同

对象类型 X 的“零偏移成员类型集” M(X) 定义为:

  • X 为非联合类且无(含继承的)非静态数据成员,则 M(X)=\emptyset
  • X 为非类、非数组类型,则 M(X)=\emptyset
  • X 为非空的非联合类类型,其首个(含继承的)非静态数据成员类型为 X_0 ,则 M(X)={X_0}\cup M(X_0)
  • X 为数组类型,其元素类型为 X_e ,则 M(X)=X_e\cup M(X_e)
  • X 为 union 类型,其所有静态数据成员类型为 X_i ,则 M(X)=\bigcup_{i}{X_i}

注意:零偏移成员类型集,是标准布局类的所有非基类子对象中,保证在零偏移位置的非基类子对象的类型集合。换言之,标准布局类强制空基类优化(empty base optimization, EBO) 。标准布局类的成员顺序完全由标准确定,而且布局中无编译器隐式维护的内存位置(虚函数或虚基类所必须的信息)。


平凡类的要求

平凡类要求(n4659 12,6):

  • 可平凡复制类(trivially copyable class)
  • 所有默认构造函数均为平凡或被删除,且至少有一个未被删除( C++20 中有改动

可平凡复制类的要求:

  • 复制构造函数、移动构造函数、复制赋值运算符、移动赋值运算符均为平凡或被删除( C++20 中有改动
  • 上述特殊成员函数至少有一个未被删除( 在 C++20 中有改动
  • 拥有平凡且未被删除的析构函数( C++20 中此条无改动,但析构函数本身有改动

平凡默认构造函数要求(n4659 15.1,6):

  • 该构造函数非用户提供(隐式定义或显式 =default
  • 类无虚函数或虚基类
  • 所有非静态数据成员均无默认成员初始化器
  • 所有直接基类均拥有平凡默认构造函数
  • 每个类类型成员(或类类型数组成员的元素)拥有平凡默认构造函数

平凡析构函数要求(n4659 15.4,6):

  • 析构函数非用户提供(隐式定义或显式 =default
  • 所有直接基类拥有平凡析构函数
  • 每个类类型成员(或类类型数组成员的元素)拥有平凡析构函数

平凡复制/移动构造函数要求(n4659 15.8.1,11):

  • 该构造函数非用户提供(隐式定义或显式 =default
  • 类无虚函数或虚基类
  • 为每个直接基类子对象选择的复制/移动构造函数为平凡
  • 为每个类类型成员(或类类型数组成员的元素)选择的复制/移动构造函数为平凡

平凡复制/移动运算符要求(n4659 15.8.2,9)

  • 该赋值运算符非用户提供(隐式定义或显式 =default
  • 类无虚函数或虚基类
  • 为每个直接基类子对象选择的复制/移动赋值运算符为平凡
  • 为每个类类型成员(或类类型数组成员的元素)选择的复制/移动赋值运算符为平凡

注意:平凡的复制/移动构造函数/赋值运算符的行为等价于用 std::memmove std::memcpy 复制对象表示。平凡的默认构造函数和析构函数的行为等价于无操作。

C++20 中的改动

C++20 中有些类模板或类模板的成员模板可以有多个同种类(n4861, 11.2)的特殊成员函数。它们有些是“合格(eligible)”的。

同种类指的是:

  • 均为默认构造函数、复制构造函数、移动构造函数、复制赋值运算符、移动赋值运算符;
  • 对于有形参的特殊成员函数,首个形参类型相同(如复制构造函数的首个形参可以是 const T& 或比较奇特的 T& ,这两种不属于同一种类);
  • 对于赋值运算符,成员函数的 cv 及引用限定相同。

这些限制保证了同种类的特殊成员函数在不考虑访问、 C++20 中加入的关联约束(通过 requires 子句)和默认参数时,在重载决议中表现相同。

合格的特殊成员函数是:

  • 未被删除;
  • 关联约束(通过 requires 子句引入)若存在则得到满足;
  • 没有更受约束的同种类特殊成员函数。

在 C++20 中可平凡复制类和平凡类的要求中,关于(预期的)析构函数外的特殊成员函数的“被删除”“未被删除”均被改成“合格”“不合格”。


析构函数的要求在字面上没有改动。但是 C++20 引入了预期的(prospected)析构函数(n4861, 11.4.7),它是 ~T() 形式的特殊成员函数,也包括析构函数。 C++20 中一个类可以有多个预期的析构函数,但其中只有重载决议在类定义末尾选择的唯一一个才成为析构函数。


附注

标量类型(含 cv 限定版本)及其数组满足标准布局类型(standard-layout type)、可平凡复制类型(trivially copyable type)、平凡类型(trivial type)的要求。

标准布局类、可平凡复制类、平凡类的数组也分别是标准布局类型、可平凡复制类型、平凡类型。

可以简单地说 POD 类型就是同时满足标准布局类型和平凡类型的类型。

C++20 中, POD 类型这一用词被弃用,未来可能会被移除。

编辑于 2021-05-07 18:06