C++的POD以及如何判断是否POD - cheeto的文章 - 知乎


在C++11及以后的版本中,POD类型(Plain Old Data)的定义被细化为两个核心概念:
平凡类型(Trivial Type)和标准布局类型(Standard Layout Type)。当类型为Trivial && Standard Layout时才能被认为是POD。

平凡类型(Trivial Type)

满足以下条件:

  • 默认构造函数:没有用户定义的构造函数,即使用默认构造函数。
  • 默认拷贝构造函数:没有用户定义的拷贝构造函数。
  • 默认析构函数:没有用户定义的析构函数。
  • 默认赋值操作符:没有用户定义的拷贝赋值和移动赋值操作符。

对于平凡类型,编译器会为其提供默认的构造、拷贝和析构行为,无需用户显式定义。

比如说以下Trivial,即使它有构造函数和析构函数 只要不是用户自定义而是default也可以

1
2
3
4
5
struct Trivial {
 int a;
 Trivial() = default;  // 默认构造函数
 ~Trivial() = default; // 默认析构函数
};

标准布局类型(Standard Layout Type)

满足以下条件:

  • 无虚函数:它没有虚函数。
  • 无虚基类:它没有虚基类。
  • 成员变量顺序:它的成员变量是按声明顺序排列的。

直接用std::is_standard_layout_v判断即可

 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
#define Print(x) std::cout << x << '\n'
struct safe {
 int m;
};
struct unsafe_cons {
 unsafe_cons(unsafe_cons const &) {}
};
struct unsafe_vir {
 virtual void foo();
};
template <typename T> class unsafe_tem {
 T data;
};
struct Trivial {
 int a;
 Trivial() = default;  // 默认构造函数
 ~Trivial() = default; // 默认析构函数
};
struct StandardLayout {
 char a; // 1 byte
 int b;  // 4 bytes
};

// 用于检查是否为 POD 类型
template <typename T> struct is_pod {
 static constexpr bool value =
 std::is_trivial<T>::value && std::is_standard_layout<T>::value &&
 std::is_trivially_default_constructible<T>::value;
};
void test1() {
 Print(is_pod<int>::value);                           // 1
 Print(is_pod<std::string>::value);                   // 0
 Print(is_pod<Trivial>::value);                       // 1
 Print(is_pod<StandardLayout>::value);             // 1
 Print(std::is_trivial<Trivial>::value);              // 1
 Print(std::is_trivial<StandardLayout>::value);    // 1
 Print(std::is_standard_layout_v<StandardLayout>); // 1
}

这里的标准布局的判定反而没有这么严格

我说的严格指的是

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct A{ 
    char a;  // 1 byte
    int b;   // 4 bytes
};

struct B{ 
 int b;   // 4 bytes
    char a;  // 1 byte
 
};

这种

struct A 会在 char 后插入填充字节,以满足 int 的对齐要求。 struct B 的内存布局使得结构体末尾需要填充字节,确保结构体的总大小满足 4 字节对齐要求。

小结

也就是说填充不影响POD的判定 而是成员变量顺序发生了改变才不算POD。

POD 类型的定义主要关注是否有特殊的构造、析构或拷贝操作,以及成员变量的顺序是否保持一致

如何判断是否POD

1
2
3
4
5
6
7
// 用于检查是否为 POD 类型
// 使用例is_pod<int>::value
template <typename T> struct is_pod {
 static constexpr bool value =
 std::is_trivial<T>::value && std::is_standard_layout<T>::value &&
 std::is_trivially_default_constructible<T>::value;
};

拓展

对于平凡类型的 classstruct,它们在内存布局、对象拷贝、传递给 C 函数等操作中,几乎没有区别。因此,可以像使用 C 语言中的结构体一样使用它们。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct Point {
    int x, y;
};

int main() {
    Point p = {1, 2};
    // 使用 reinterpret_cast 强制转换
    void* ptr = &p;  // void* 指针指向结构体
    // 使用 reinterpret_cast 强制转换为 Point* 类型
    Point* p2 = reinterpret_cast<Point*>(ptr);
    // 访问成员
    std::cout << "x: " << p2->x << ", y: " << p2->y << std::endl;
    return 0;
}