C++中的POD型别
最近在阅读《STL源码刨析》的过程中,遇到了POD型别,书中的解释是:
POD意指Plain Old Data,也就是标量型别(scalar types)或传统的C struct型别。POD型别必然拥有trivial ctor/dctor/copy/assignment operator函数。(这里的trivial 函数意思是该函数无光痛痒,不重要,即平凡函数)
但是看完这个解释,我还是没有很清楚的理解所谓POD型别,通过在网上查阅资料,我对POD有了更好的理解。网上讲到一种判断POD型别的方式,即当一个数据类型同时满足”平凡的定义“和”标准布局“,我们可以认为其是一个POD型别的数据。
平凡的定义
对于一个类或结构体来讲,平凡的定义是:
- 平凡的构造/拷贝构造/移动构造函数
- 平凡的拷贝复制/移动复制运算符
- 平凡的析构函数
- 不包含虚函数、虚基类
测试代码:
#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 main() { std::cout << "有不平凡的构造函数" << ends << std::is_trivial<A>::value << std::endl; std::cout << "有不平凡的拷贝构造函数" << ends << std::is_trivial<B>::value << std::endl; std::cout << "有不平凡的移动构造函数" << ends << std::is_trivial<C>::value << std::endl; std::cout << "有不平凡的拷贝赋值运算符" << ends << std::is_trivial<D>::value << std::endl; std::cout << "有不平凡的移动赋值运算符" << ends << std::is_trivial<E>::value << std::endl; std::cout << "有不平凡的析构函数" << ends << std::is_trivial<F>::value << std::endl; std::cout << "有虚函数" << ends << std::is_trivial<G>::value << std::endl; std::cout << "有虚基类" << ends << std::is_trivial<H>::value << std::endl; std::cout << "平凡的类" << ends << std::is_trivial<I>::value << std::endl; system("pause"); return 0; }
运行结果:
测试环境:VS2019
这里所提到的平凡的构造、析构函数,就是指编译器自动生成的构造、析构函数等,也就是说一个POD型别的数据是不能有用户自定义的构造/拷贝构造/移动构造函数,拷贝复制/移动复制运算符以及析构函数。
- 子类的第一个非静态成员不可以是基类类型
- 没有虚函数、虚基类
- 所有非静态成员都符合标准布局类型
测试代码:
#include <iostream> using namespace std; //成员a和b具有不同的访问权限 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 {}; //非静态成员x不符合标准布局类型 class F { A x; }; int main() { 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; }
运行结果:
测试环境:VS2019
为什么C++需要引进POD这一个概念?我结合自己的理解和网上的资料总结出以下几点:
- POD型别是兼容传统的C struct的,在需要与C语言交接的地方起作用
- 提升效率。《STL源码剖析》中讲到,如果一个数组的成员是POD型别的数据,那么在对该数组进行拷贝操作的时候,可以直接调用更为底层的函数进行操作,而不需要对每一个成员使用复制赋值构造函数
- 一个POD型别的类或结构体通过二进制拷贝后还能保持其数据不变