在各种性能优化的场景,特别是关于自定义数据结构的设计和实现,我们会遇到关于PODs类型的讨论。
那么 ,什么是POD Type呢?它有哪些好处,以及使用场景是什么?今天,这个话题我们来讨论讨论。
定义[wikipedia]
Passive data structures are appropriate when there is a part of a system where it should be clearly indicated that the detailed logic for data manipulation and integrity are elsewhere. PDSs are often found at the boundaries of a system, where information is being moved to and from other systems or persistent storage and the problem domain logic that is found in other parts of the system is irrelevant. For example, PDS would be convenient for representing the field values of objects that are being constructed from external data, in a part of the system where the semantic checks and interpretations needed for valid objects are not applied yet.
A PDS type in C++, or Plain Old C++ Object, is defined as either a scalar type or a PDS class.[2] A PDS class has no user-defined copy assignment operator, no user-defined destructor, and no non-static data members that are not themselves PDS. Moreover, a PDS class must be an aggregate, meaning it has no user-declared constructors, no private nor protected non-static data, no virtual base classes[a] and no virtual functions.[4] The standard includes statements about how PDS must behave in C++. The type_traits library in the C++ Standard Library provides a template named is_pod that can be used to determine whether a given type is a POD.[5] In C++20 the notion of "plain old data" (POD) and by that is_pod is deprecated and replaced with the concept of "trivial" and "standard-layout" types.[6]
In some contexts, C++ allows only PDS types to be used. For example, a union in C++98 cannot contain a class that has virtual functions or nontrivial constructors or destructors. This restriction is imposed because the compiler cannot determine which constructor or destructor should be called for a union. PDS types can also be used for interfacing with C, which supports only PDS.
在 C++ 中,POD(Plain Old Data,简单旧数据)类型是指那些与 C 语言数据类型兼容的类或结构体类型,它们可以通过二进制拷贝保持数据不变。POD 类型可以进行底层操作,如内存复制、比较和序列化等。C++11 引入了"平凡类型"(TrivialType)和"标准布局类型"(StandardLayoutType)的概念,以更精确地描述 POD 类型的特征。
一个类型要成为 POD 类型,必须满足以下条件:
-
平凡类型(Trivial Type):必须拥有平凡的构造函数、析构函数、拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符。这意味着这些函数要么由编译器提供,要么声明为 default。此外,类不能包含虚函数和虚基类。
-
标准布局类型(Standard Layout Type):类或结构体的布局必须标准化,意味着所有非静态数据成员具有相同的访问权限,没有虚函数或虚基类,所有非静态数据成员和基类都是标准布局类型。
-
没有非静态的非 POD 成员:类中的所有非静态数据成员也必须是 POD 类型。
-
没有引用类型的成员。
-
没有用户定义的复制构造函数和析构函数。
C++20 标准中已经弃用了 POD 类型的概念,转而使用更加精确的类型特质,如 std::is_trivially_copyable、std::is_trivial 和 std::is_standard_layout 来确定类型是否具有 POD 类型的特征。
好处
POD 类型的一个关键优势是它们可以安全地与 C 语言库进行交互,因为它们的内存布局是连续的,可以使用 memcpy 和 memset 等函数进行操作。此外,POD 类型的静态初始化是安全有效的,因为它们在程序启动时不需要构造函数进行初始化。
如何判断?
在实际使用中,可以通过 std::is_pod 模板来检查一个类型是否为 POD 类型,但请注意,这个模板自 C++20 开始已被弃用。取而代之的是使用 std::is_trivial 和 std::is_standard_layout 来分别检查类型的平凡性和标准布局性。
A POD class is a class that is both trivial (can only be statically initialized) and standard-layout (has a simple data structure), and thus is mostly restricted to the characteristics of a class that are compatible with those of a C data structure declared with struct or union in that language, even though the expanded C++ grammar can be used in their declaration and can have member functions.
is_pod inherits from integral_constant as being either true_type or false_type, depending on whether T is a POD type.
c++
template <class T> struct is_pod;
T: A complete type, or void (possible cv-qualified), or an array of unknown bound of a complete element type.
例子:
c++
// is_pod example
#include <iostream>
#include <type_traits>
struct A { int i; }; // C-struct (POD)
class B : public A {}; // still POD (no data members added)
struct C : B { void fn(){} }; // still POD (member function)
struct D : C { D(){} }; // no POD (custom default constructor)
int main() {
std::cout << std::boolalpha;
std::cout << "is_pod:" << std::endl;
std::cout << "int: " << std::is_pod<int>::value << std::endl;
std::cout << "A: " << std::is_pod<A>::value << std::endl;
std::cout << "B: " << std::is_pod<B>::value << std::endl;
std::cout << "C: " << std::is_pod<C>::value << std::endl;
std::cout << "D: " << std::is_pod<D>::value << std::endl;
return 0;
}
要求&条件
在 C++ 中,POD(Plain Old Data,简单旧数据)类型的内存布局需要满足以下具体要求:
-
连续的内存布局:POD 类型的成员在内存中是连续排列的,没有空隙(除了那些可能由于对齐要求而存在的填充)。
-
成员的地址顺序:成员的地址顺序与它们在类定义中声明的顺序相同。
-
没有虚函数:POD 类型不能包含虚函数。虚函数通常需要一个虚表指针(vptr),这会破坏内存的连续布局。
-
没有虚基类:类中不能有虚基类,因为虚基类的存在会影响对象的内存布局。
-
成员的访问权限:所有非静态数据成员必须具有相同的访问权限(public、protected 或 private),以便保持内存布局的一致性。
-
基类要求:如果一个类继承自其他类,那么它的基类也必须是 POD 类型,并且派生类的第一个非静态数据成员的类型不能与任何基类的类型相同。
-
没有引用类型的成员:POD 类型不能包含引用类型的成员。
-
没有用户定义的构造函数和析构函数:POD 类型的构造函数和析构函数必须是平凡的,这意味着它们不能执行任何操作,不能有自定义的实现。
-
没有用户定义的赋值运算符:POD 类型的拷贝赋值运算符和移动赋值运算符也必须是平凡的。
-
没有私有或受保护的非静态数据成员:在 C++17 之前,POD 类型不能有私有或受保护的非静态数据成员。从 C++17 开始,这个要求放宽了,POD 类型可以有私有或受保护的非静态数据成员。
-
标准布局:POD 类型必须是标准布局类型,这意味着它们的内存布局是标准化的,可以被 C 语言程序所理解。
-
POD 类型的数组:POD 类型的数组也是 POD 类型,因为数组的元素在内存中是连续存储的。
这些要求确保了 POD 类型的对象可以用简单的字节复制(如使用 memcpy)来进行拷贝,这在与 C 语言交互或进行底层内存操作时非常有用。
在 C++11 及以后的版本中,可以使用·std::is_pod
来检查一个类型是否是 POD 类型,但这个特性在 C++20 中已被弃用。取而代之的是使用 std::is_trivial
和 std::is_standard_layout
来检查类型的平凡性和标准布局性。
进一步讨论
更深刻的讨论见: