C++ POD 类型
POD 是 C++ 中一个比较重要的概念,POD 是英文 Plain Old Data 的缩写(通俗讲就是类或结构体通过二进制拷贝后还能保持其数据不变),用来描述一个类型(包括 class、union 和 struct等)的属性。其中Plain表示这个类型是个平凡的类,Old表示其与C的兼容性。C++11 中将划分为两个基本概念:平凡的(trivial)和标准布局(standard layout)。
POD的好处:
- 字节赋值,可以放心使用memset和memcpy对POD类型进行初始化和拷贝。
- 提供对C内存兼容。POD类型数据在C与C++间的操作总是安全的。
- 保证静态初始化的安全有效。POD类型对象初始化往往更简单。
1、平凡性(trivial)
一个trivial class 或struct 应符合以下定义:
- 拥有平凡的默认构造函数(trivial constructor)和析构函数(trivial destructor)。如果用户定义了构造函数或析构函数,则不能称为POD类型。
- 拥有平凡的赋值构造函数(trivial copy constructor)和移动构造函数(trivial move constructor)。
- 拥有平凡的复制赋值运算符(trivial assignment operator)和移动赋值运算符(trivial move operator)。
- 不能包含虚函数和虚基类。
在c++11中可以使用default关键字来显示声明缺省版本的构造函数从而是类型恢复平凡化(第二、第三规则类似),同时c++11也提供了一个类模板来帮助我们识别一个类是否平凡的。
template <typename T>struct std::is_trival
下列中A1、A3为trivial类,A2为非trivial类
class A1 {}; class A2 { public: A2(){} }; class A3 { public: A3() = default; A3(int a3){} }; int main() { cout<<boolalpha<<is_trivial<A1>::value<<endl; cout<<boolalpha<<is_trivial<A2>::value<<endl; cout<<boolalpha<<is_trivial<A3>::value<<endl; return 0; }
2、标准布局(standard layout)
满足如下条件的类或结构体是标准布局的:
- 所有非静态成员具有相同的访问权限,比如都是private权限,或者都是public,或者都为protected。
- 在类或结构体的继承时,满足以下两种情况之一:
- 派生类中有非静态成员,且只有一个仅包含静态成员的基类。、
- 基类有非静态成员,而派生类没有非静态成员。
class B1 { static int n; }; class B2 : B1 { int n1; }; //派生类 B2 中有非静态成员,且只有一个仅包含静态成员的基类 B1,所以B2为标准布局。 class B3 : B2 { static int n2; }; //基类 B2 有非静态成员,而派生类 B3 没有非静态成员,所以B3为标准布局。
- 类中第一个非静态成员的类型与基类不同。(基于c++中优化不包括成员的基类而产生的,在c++标准中,如果基类没有成员,基类不应占空间,所以标准允许派生类的第一个成员与基类共享地址,此时基类并没有占据任何实际空间。但此时若派生类第一个成员类型仍然是基类,编译器会为基类分配一个字节空间,因为c++标准要求类型相同的对象地址不同,因此c++11强制要求POD类型的派生类第一个非静态成员类型必须不同于基类)
class A1 {}; class A2 {}; class B1:public A1 { public: A1 a1; int b1; }; class B2:public A1 { public: A2 a2; int b2; }; class B3:public A1 { public: int b3; A1 a1; }; int main() { B1 b1;b1.b1=0xb1; B2 b2;b2.b2=0xb2; B3 b3;b3.b3=0xb3; cout<<"sizeof(b1)="<<sizeof(b1)<<endl; cout<<"&b1 ="<<&b1<<endl; cout<<"&b1.a1="<<&b1.a1<<endl; cout<<"&b1.b1="<<&b1.b1<<endl<<endl; cout<<"sizeof(b2)="<<sizeof(b2)<<endl; cout<<"&b2 ="<<&b2<<endl; cout<<"&b2.a2="<<&b2.a2<<endl; cout<<"&b2.b2="<<&b2.b2<<endl<<endl; cout<<"sizeof(b3)="<<sizeof(b3)<<endl; cout<<"&b3 ="<<&b3<<endl; cout<<"&b3.b3="<<&b3.b3<<endl; cout<<"&b3.a1="<<&b3.a1<<endl<<endl; cout<<boolalpha<<is_standard_layout<B1>::value<<endl; cout<<boolalpha<<is_standard_layout<B2>::value<<endl; cout<<boolalpha<<is_standard_layout<B3>::value<<endl; return 0; } /* * * sizeof(b1)=8 * &b1 =0x62fe48 * &b1.a1=0x62fe49 * &b1.b1=0x62fe4c * * sizeof(b2)=8 * &b2 =0x62fe40 * &b2.a2=0x62fe40 * &b2.b2=0x62fe44 * * sizeof(b3)=8 * &b3 =0x62fe38 * &b3.b3=0x62fe38 * &b3.a1=0x62fe3c * * false * true * true * */
- 没有虚拟函数和虚拟基类。
- 所有非静态成员均符合标准布局类型,其基类也符合标准布局。
3、POD的使用
当一个数据类型满足“trivial”和“standard layout”,我们则认为它是POD数据库。
c++ 提供了模板来判断一个类或结构体对象是否标准布局
template <typename T> structstd::is_standard_layout; //头文件为<type_traits> template <typename T> struct std::is_pod //判断一个类型是否是POD,头文件为<type_traits>
所有兼容C语言的数据类型都是 POD 类型(struct、union 等不能违背上述规则)。
用例:
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; } /* 输出: * before * 8 * 10.5 * after * 8 * 10.5 */
通过运行结果,可以看到,对一个POD类型进行二进制拷贝后,数据成功进行了迁移。