is pod
POD(Plain Old Data,普通旧数据)类型是从 C++11 开始引入的概念,Plain 代表一个对象是一个普通类型,Old 代表一个对象可以与 C 兼容。通俗地讲,一个类、结构、共用体对象或非构造类型对象能通过二进制拷贝(如 memcpy())后还能保持其数据不变正常使用的就是POD类型的对象。严格来讲,一个对象既是普通类型(Trivial Type)又是标准布局类型(Standard-layout Type)那么这个对象就是 POD 类型。
不同类型的对象意味着对象的成员在内存中的布局是不同的。在某些情况下,布局是有规范明确的定义,但如果类或结构包含某些 C++ 语言功能,如虚拟基类、 虚函数、 具有不同的访问控制的成员,则不同编译器会有不同的布局实现,具体取决于编译器对代码的优化方式,比如实现内存对齐,减少访存指令周期。例如,如果类具有虚函数,该类的所有实例都会包含一个指向虚函数表的指针,那么这个对象就不能直接通过二进制拷贝的方式传到其它语言编程的程序中使用。
C++ 给定对象类型取决于其特定的内存布局方式,一个对象是普通、标准布局还是 POD 类型,可以根据标准库函数模板来判断:
is_trivial<T>
is_standard_layout<T>
is_pod<T>
1
2
3
使用时需要包含头文件<type_traits>。
1.普通类型
当类或结构体同时满足如下几个条件时是普通类型:
(1)没有虚函数或虚基类;
(2)由C++编译器提供默认的特殊成员函数(默认的构造函数、拷贝构造函数、移动构造函数、赋值运算符、移动赋值运算符和析构函数);
(3)数据成员同样需要满足条件(1)和(2)。
注意,普通类型可以具有不同的访问说明符。下面我们使用模版类std::is_trivial<T>::value来判断数据类型是否为普通类型。
#include <iostream>
#include <string>
class A { A() {} };
class B { B(B&) {} };
class C { C(C&&) {} };
class D { D operator=(D&) {} };
class E { E operator=(E&&) {} };
class F { ~F() {} };
class G { virtual void foo() = 0; };
class H : virtual F{};
class I {};
int main()
{
std::cout << std::is_trivial<A>::value ; //有自定义构造函数
std::cout << std::is_trivial<B>::value; //有自定义的拷贝构造函数
std::cout << std::is_trivial<C>::value; //有自定义的移动构造运算符
std::cout << std::is_trivial<D>::value; //有自定义的赋值运算符
std::cout << std::is_trivial<E>::value; //有自定义的移动赋值运算符
std::cout << std::is_trivial<F>::value; //有自定义的析构函数
std::cout << std::is_trivial<G>::value; //有虚函数
std::cout << std::is_trivial<H>::value; //有虚基类
std::cout << std::is_trivial<I>::value ; //普通的类return 0;
}
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
程序输出结果如下:
000000001
1
2.标准布局类型
当类或结构体同时满足如下几个条件时是标准布局类型:
(1)没有虚函数或虚基类;
(2)所有非静态数据成员都具有相同的访问说明符;
(3)在继承体系中最多只有一个类中有非静态数据成员;
(4)子类中的第一个非静态成员的类型与其基类不同;此规则是因为 C++ 允许优化不包含成员基类而产生的。在 C++ 标准中,如果基类没有任何数据成员,基类应不占用空间,为了体现这一点,C++ 标准允许派生类的第一个成员与基类共享同一地址空间。但是如果派生类的第一个非静态成员的类型和基类相同,由于 C++ 标准要求相同类型的对象的地址必须不同,编译器就会为基类分派一个字节的地址空间。比如下面的代码:
class B1{};
class B2{};
class D1: public B1
{
B1 b;
int i ;
};
class D2: public B1
{
B2 b ;
int i ;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
D1 和 D2 类型的对象内存布局应该是相同的,但实际上是不同的,因为 D1 中基类 B1 和对象 b 都占用了 1 个字节,D2 中基类 B1 为空,并不占用内存空间。D1 和 D2 的内容布局从左至右如下图所示:
这里写图片描述
注意,这条规定 GNU C++ 遵守,Visual C++ 并不遵守。
(5)所有非静态数据成员同样需要满足条件(1)、(2)、(3)和(4)。
考察如下程序:
#include <iostream>
using namespace std;
class A { virtual void foo() = 0; };
class B
{
private:
int a;
public:
int b;
};
class C1
{
int x1;
};
class C:C1
{
int x;
};
class D1 {};
class D : D1
{
D1 d1;
};
class E : virtual C1 {};
class F { B x; };
class G :C1, D1 {};
int main()
{
std::cout << std::is_standard_layout<A>::value ; // 有虚函数
std::cout << std::is_standard_layout<B>::value ; // 成员a和b具有不同的访问权限
std::cout << std::is_standard_layout<C>::value ; // 继承树有非静态数据成员的类超过1个
std::cout << std::is_standard_layout<D>::value ; // 第一个非静态成员是基类类型
std::cout << std::is_standard_layout<E>::value ; // 有虚基类
std::cout << std::is_standard_layout<F>::value ; // 非静态成员x不符合标准布局类型
std::cout << std::is_standard_layout<G>::value ; // return 1
return 0;
}
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
39
40
41
42
43
44
程序运行结果:
00000001
1
3.POD 类型简介
一个对象既是普通类型(Trivial Type)又是标准布局类型(Standard-layout Type)那么这个对象就是POD类型。为什么我们需要 POD 类型满足这些条件呢,POD 类型在源码层级的操作上兼容于 ANSI C。POD 对象与 C 语言中的对象具有一些共同的特性,包括初始化、复制、内存布局与寻址:
(1)可以使用字节赋值,比如用 memset、memcpy 对 POD 类型进行赋值操作;
(2)对 C 内存布局兼容,POD 类型的数据可以使用 C 函数进行操作且总是安全的;
(3)保证了静态初始化的安全有效,静态初始化可以提高性能,如将 POD 类型对象放入 BSS 段默认初始化为 0。
下面看一下 POD 类型的二进制拷贝示例:
#include <iostream>
using namespace std;
class A
{
public:
int x;
double y;
};
int main()
{
if (std::is_pod<A>::value)
{
std::cout << "before" << std::endl;
A a;
a.x = 8;
a.y = 10.5;
std::cout << a.x << std::endl;
std::cout << a.y << std::endl;
size_t size = sizeof(a);
char *p = new char[size];
memcpy(p, &a, size);
A *pA = (A*)p;
std::cout << "after" << std::endl;
std::cout << pA->x << std::endl;
std::cout << pA->y << std::endl;
delete p;
}
return 0;
}
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
程序运行结果如下:
before
8
10.5
after
8
10.5
1
2
3
4
5
6
可见,POD 类型使用字节拷贝可以正常进行赋值操作。
事实上,如果对象是普通类型,不是标准布局,例如类同时有 public 与 private 的非静态数据成员,也可以使用 memcpy 进行字节拷贝赋值。如果对象是标准布局类型,不是普通类型,例如类有复杂的 move 与 copy 构造函数,也可以使用 C 函数进行操作。
————————————————
版权声明:本文为CSDN博主「Dablelv」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/K346K346/java/article/details/81534100
本编继续介绍第三章的内容:POD类型,plain old data。Plain代表数据是普通类型,old代表能与C兼容支持memcpy、memset等函数。POD分为两个部分,trivial(平凡的)和(s’tan’dard layout)标准布局的,必须同时满足才是POD类型。
平凡的类或结构体必须满足以下的条件:
- 平凡的默认构造函数和析构函数。只要是自己定义了函数,即使实现为空,也不再平凡。所以就是说不能自定义默认构造函数和析构函数。
- 平凡的拷贝构造函数和移动构造函数。
- 平凡的赋值构造函数。
- 不能包含虚函数和虚基类。
另外,可以用std::is_trivial来判断是不是一个平凡类型,std::is_trivial<T>::value,例如:
class TrivialA {
};
class TrivialB {
public :
int a;
};
class TrivialC {
TrivialC() {} // 有默认构造函数,不平凡
};
class TrivialD {
TrivialD(const TrivialD& a) {} // 有赋值构造函数,不平凡
};
class TrivialE {
TrivialE(TrivalE&& a) {} // 有移动构造函数,不平凡
};
class TrivialF {
TrivialF& operator=(const TrivialF& a) {} // 有赋值构造函数,不平凡
};
class TrivialG {
virtual void func() = 0; // 有虚函数,不平凡
};
class TrivialH: virtual public TrivialA { // 有虚基类,不平凡
};
int main(int argc, char **argv)
{
std::cout << std::is_trivial<TrivialA>::value << std::endl; // 1
std::cout << std::is_trivial<TrivialB>::value << std::endl; // 1
std::cout << std::is_trivial<TrivialC>::value << std::endl; // 从这里开始都是0
std::cout << std::is_trivial<TrivialD>::value << std::endl;
std::cout << std::is_trivial<TrivialE>::value << std::endl;
std::cout << std::is_trivial<TrivialF>::value << std::endl;
std::cout << std::is_trivial<TrivialG>::value << std::endl;
std::cout << std::is_trivial<TrivialH>::value << std::endl;
return 0;
}
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
标准布局的类或结构体必须满足以下的条件:
所有非静态成员有相同的访问权限。
在类或结构体继承时满足以下两个条件之一:
1、派生类中有非静态成员,且只有仅包含静态成员的基类。
2、基类有非静态成员,而派生类没有非静态成员。
其实就是派生类和基类中不允许同时出现非静态成员,因为同时有非静态成员就无法进行memcpy
类中第一个非静态成员的类型与基类不同。
C++标准允许,在基类没有成员时,派生类第一个成员与基类共享地址。但是当派生类中第一个数据成员类型为基类类型时,有趣的问题就来了。首先,这时派生类的内存布局包括基类部分的内存布局,同时自己又添加了另外一个基类类型的变量,如果编译器优化实现第一个成员和基类部分共享地址,那么就违背了C++标准的另一个要求,同类型的不同对象地址必须不同。
没有虚函数和虚基类。
所有非静态成员均符合标准布局,其基类也符合标准布局。
另外,可以用std::is_standard_layout来判断是不是一个标准布局,std::is_standard_layout<T>::value,例如:
class StdLayoutA {
};
class StdLayoutB {
public :
int a;
int b;
};
class StdLayoutC : public StdLayoutA {
public:
int a;
int b;
void fun() {}
};
class StdLayoutD : public StdLayoutA {
public:
int a;
StdLayoutA sla;
};
class StdLayoutE : public StdLayoutA , public StdLayoutC {
};
class StdLayoutF {
public:
static int a;
};
class StdLayoutG : public StdLayoutF {
public:
int a;
};
class StdLayoutH: public StdLayoutA { // 第一个非静态成员是基类,所以不标准,如果sla的位置和b交换,那么是标准的
public:
StdLayoutA sla;
int b;
};
class StdLayoutI : public StdLayoutB { // 基类和派生类都有非静态变量,所以不标准,如果基类或者派生类中只有静态变量,那么是标准的
public:
int a;
};
class StdLayoutJ: public StdLayoutI { // 基类不标准,所以不标准,如果StdLayoutI标准,那么是标准的
};
class StdLayoutK{ // 非静态成员权限不同,所以不标准,如果a或b有一个是静态,那么是标准的
public:
int a;
private:
int b;
};
int main(int argc, char **argv)
{
std::cout << std::is_standard_layout<StdLayoutA>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutB>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutC>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutD>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutE>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutF>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutG>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutH>::value << std::endl; // 以上都是1,从这里开始都是0
std::cout << std::is_standard_layout<StdLayoutI>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutJ>::value << std::endl;
std::cout << std::is_standard_layout<StdLayoutK>::value << std::endl;
return 0;
}
————————————————
版权声明:本文为CSDN博主「WizardtoH」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/WizardtoH/java/article/details/80767740
啥是POD类型?
POD全称Plain Old Data。通俗的讲,一个类或结构体通过二进制拷贝后还能保持其数据不变,那么它就是一个POD类型。
平凡的定义
1.有平凡的构造函数
2.有平凡的拷贝构造函数
3.有平凡的移动构造函数
4.有平凡的拷贝赋值运算符
5.有平凡的移动赋值运算符
6.有平凡的析构函数
7.不能包含虚函数
8.不能包含虚基类
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
#include "stdafx.h"
#include <iostream>
using namespace std;
class A { A(){} };
class B { B(B&){} };
class C { C(C&&){} };
class D { D operator=(D&){} };
class E { E operator=(E&&){} };
class F { ~F(){} };
class G { virtual void foo() = 0; };
class H : G {};
class I {};
int _tmain(int argc, _TCHAR* argv[])
{
std::cout << std::is_trivial<A>::value << std::endl; // 有不平凡的构造函数
std::cout << std::is_trivial<B>::value << std::endl; // 有不平凡的拷贝构造函数
std::cout << std::is_trivial<C>::value << std::endl; // 有不平凡的拷贝赋值运算符
std::cout << std::is_trivial<D>::value << std::endl; // 有不平凡的拷贝赋值运算符
std::cout << std::is_trivial<E>::value << std::endl; // 有不平凡的移动赋值运算符
std::cout << std::is_trivial<F>::value << std::endl; // 有不平凡的析构函数
std::cout << std::is_trivial<G>::value << std::endl; // 有虚函数
std::cout << std::is_trivial<H>::value << std::endl; // 有虚基类
std::cout << std::is_trivial<I>::value << std::endl; // 平凡的类
system("pause");
return 0;
}
运行结果
标准布局的定义
1.所有非静态成员有相同的访问权限
2.继承树中最多只能有一个类有非静态数据成员
3.子类的第一个非静态成员不可以是基类类型
4.没有虚函数
5.没有虚基类
6.所有非静态成员都符合标准布局类型
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
#include "stdafx.h"
#include <iostream>
using namespace std;
class A
{
private:
int a;
public:
int b;
};
class B1
{
static int x1;
};
class B2
{
int x2;
};
class B : B1, B2
{
int x;
};
class C1 {};
class C : C1
{
C1 c;
};
class D { virtual void foo() = 0; };
class E : D {};
class F { A x; };
int _tmain(int argc, _TCHAR* argv[])
{
std::cout << std::is_standard_layout<A>::value << std::endl; // 违反定义1。成员a和b具有不同的访问权限
std::cout << std::is_standard_layout<B>::value << std::endl; // 违反定义2。继承树有两个(含)以上的类有非静态成员
std::cout << std::is_standard_layout<C>::value << std::endl; // 违反定义3。第一个非静态成员是基类类型
std::cout << std::is_standard_layout<D>::value << std::endl; // 违反定义4。有虚函数
std::cout << std::is_standard_layout<E>::value << std::endl; // 违反定义5。有虚基类
std::cout << std::is_standard_layout<F>::value << std::endl; // 违反定义6。非静态成员x不符合标准布局类型
system("pause");
return 0;
}
运行结果
POD的使用
当一个数据类型满足了”平凡的定义“和”标准布局“,我们则认为它是一个POD数据。可以通过std::is_pod来判断一个类型是否为POD类型。
如文章开头说的,一个POD类型是可以进行二进制拷贝的,看看下面的例子。
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
using namespace std;
class A
{
public:
int x;
double y;
};
int _tmain(int argc, _TCHAR* argv[])
{
if (std::is_pod<A>::value)
{
std::cout << "before" << std::endl;
A a;
a.x = 8;
a.y = 10.5;
std::cout << a.x << std::endl;
std::cout << a.y << std::endl;
size_t size = sizeof(a);
char *p = new char[size];
memcpy(p, &a, size);
A *pA = (A*)p;
std::cout << "after" << std::endl;
std::cout << pA->x << std::endl;
std::cout << pA->y << std::endl;
delete p;
}
system("pause");
return 0;
}
运行结果
可以看到,对一个POD类型进行二进制拷贝后,数据都成功的迁移过来了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理