【1】什么是POD类型?
Plain old data structure,缩写为POD,Plain代表是一种普通类型,Old体现该类型的对象可以与C兼容。
POD类型是C++语言标准中定义的一类数据结构,适用于需要明确的数据底层操作的系统中。
POD通常被用在系统的边界处,即指不同系统之间只能以底层数据的形式进行交互,系统的高层逻辑不能互相兼容。
比如,当对象的字段值是从外部数据中构建时,系统还没有办法对对象进行语义检查和解释,这时就适用POD来存储数据。
严格来讲,一种既是普通类型(Trivial Type)又是标准布局类型(Standard-layout Type)的类型,即是POD类型。
通俗地讲,一种类型的对象通过二进制拷贝(如memcpy())后还能保持数据不变可正常使用的类型,即是POD类型。
【2】为什么引入POD类型?
不同类型的对象,本质区别在于对象的成员在内存中的布局是不同的。
在某些情况下,布局是有明确规范的定义,但如果类或结构包含某些C++语言功能,如虚基类、虚函数、具有不同的访问控制的成员等,
则不同编译器会有不同的布局实现,具体取决于编译器对代码的优化方式,比如实现内存对齐,减少访存指令周期等。
例如,如果类具有虚函数,该类的所有实例都会包含一个指向虚函数表的指针,那么这个对象就不能直接通过二进制拷贝的方式传到其它语言编程的程序中使用。
C++给定对象的类型取决于其特定的内存布局方式,一个对象是普通、标准布局还是POD类型,可以根据标准库函数模板来判断:
1 #include <type_traits> 2 #include <iostream> 3 using namespace std; 4 5 int main() 6 { 7 cout << is_trivial<int>::value << endl; // 1 8 cout << is_standard_layout<int>::value << endl; // 1 9 cout << is_pod<int>::value << endl; // 1 10 11 system("pause"); 12 }
POD对象与C语言中的对象具有一些共同的特性,包括初始化、复制、内存布局与寻址:
(1)可以使用字节赋值,比如用memset、memcpy对POD类型进行赋值操作;
(2)对C内存布局兼容,POD类型的数据可以使用C函数进行操作且总是安全的;
(3)保证了静态初始化的安全有效,静态初始化可以提高性能,如将POD类型对象放入BSS段默认初始化为0。
【3】POD类型需要满足什么条件?
POD类型既要满足平凡类型的条件又要满足标准布局类型的条件。
(1)平凡类型
[1] 有平凡的构造函数
[2] 有平凡的拷贝构造函数
[3] 有平凡的移动构造函数
[4] 有平凡的拷贝赋值运算符
[5] 有平凡的移动赋值运算符
[6] 有平凡的析构函数
[7] 不能包含虚函数
[8] 不能包含虚基类
如下代码示例:
1 #include <type_traits> 2 #include <iostream> 3 using namespace std; 4 5 class A { A() {} }; 6 class B { B(B&) {} }; 7 class C { C(C&&) {} }; 8 class D { D operator=(D&) {} }; 9 class E { E operator=(E&&) {} }; 10 class F { ~F() {} }; 11 class G { virtual void foo() = 0; }; 12 class H : G {}; 13 class I {}; 14 15 int main() 16 { 17 std::cout << std::is_trivial<A>::value << std::endl; // 有不平凡的构造函数 18 std::cout << std::is_trivial<B>::value << std::endl; // 有不平凡的拷贝构造函数 19 std::cout << std::is_trivial<C>::value << std::endl; // 有不平凡的拷贝赋值运算符 20 std::cout << std::is_trivial<D>::value << std::endl; // 有不平凡的拷贝赋值运算符 21 std::cout << std::is_trivial<E>::value << std::endl; // 有不平凡的移动赋值运算符 22 std::cout << std::is_trivial<F>::value << std::endl; // 有不平凡的析构函数 23 std::cout << std::is_trivial<G>::value << std::endl; // 有虚函数 24 std::cout << std::is_trivial<H>::value << std::endl; // 有虚基类 25 26 std::cout << std::is_trivial<I>::value << std::endl; // 平凡的类 27 28 system("pause"); 29 return 0; 30 } 31 32 /*运行结果 33 0 34 0 35 0 36 0 37 0 38 0 39 0 40 0 41 1 42 */
(2)标准布局定义
[1] 所有非静态成员有相同的访问权限
[2] 继承树中最多只能有一个类有非静态数据成员
[3] 子类的第一个非静态成员不可以是基类类型
[4] 没有虚函数
[5] 没有虚基类
[6] 所有非静态成员都符合标准布局类型
如下代码示例:
1 #include <type_traits> 2 #include <iostream> 3 using namespace std; 4 5 class A 6 { 7 private: 8 int a; 9 public: 10 int b; 11 }; 12 13 class B1 14 { 15 static int x1; 16 }; 17 18 class B2 19 { 20 int x2; 21 }; 22 23 class B : B1, B2 24 { 25 int x; 26 }; 27 28 class C1 {}; 29 class C : C1 30 { 31 C1 c; 32 }; 33 34 class D { virtual void foo() = 0; }; 35 class E : D {}; 36 class F { A x; }; 37 38 int main() 39 { 40 std::cout << std::is_standard_layout<A>::value << std::endl; // 违反定义1:成员a和b具有不同的访问权限 41 std::cout << std::is_standard_layout<B>::value << std::endl; // 违反定义2:继承树有两个(含)以上的类有非静态成员 42 std::cout << std::is_standard_layout<C>::value << std::endl; // 违反定义3:第一个非静态成员是基类类型 43 std::cout << std::is_standard_layout<D>::value << std::endl; // 违反定义4:有虚函数 44 std::cout << std::is_standard_layout<E>::value << std::endl; // 违反定义5:有虚基类 45 std::cout << std::is_standard_layout<F>::value << std::endl; // 违反定义6:非静态成员x不符合标准布局类型 46 47 system("pause"); 48 return 0; 49 } 50 51 /*运行结果 52 0 53 0 54 0 55 0 56 0 57 0 58 */
【4】POD类型的应用
当一个数据类型满足了“平凡的定义”和“标准布局定义”,我们则认为它是一个POD数据。
一个POD类型是可以进行二进制拷贝的,如下代码示例:
1 #include <type_traits> 2 #include <iostream> 3 using namespace std; 4 5 class A 6 { 7 public: 8 int x; 9 double y; 10 }; 11 12 int main() 13 { 14 if (std::is_pod<A>::value) 15 { 16 std::cout << "before" << std::endl; 17 A a; 18 a.x = 8; 19 a.y = 10.5; 20 std::cout << a.x << std::endl; 21 std::cout << a.y << std::endl; 22 23 size_t size = sizeof(a); 24 char* p = new char[size]; 25 memcpy(p, &a, size); 26 A* pA = (A*)p; 27 28 std::cout << "after" << std::endl; 29 std::cout << pA->x << std::endl; 30 std::cout << pA->y << std::endl; 31 32 delete p; 33 } 34 35 system("pause"); 36 return 0; 37 } 38 39 /*运行结果 40 before 41 8 42 10.5 43 after 44 8 45 10.5 46 */
很明显,对一个POD类型的对象进行二进制拷贝后,数据可成功迁移。
good good study, day day up.
顺序 选择 循环 总结