一、POD类型与普通类型和标准布局类型的的关系。
POD(Plain Old Data,普通旧类型)是从C++11开始引入的概念,Plain代表一个对象是一个普通类型,Old代表一个对象可以与
C兼容。通俗的讲,一个类、结构、共用体对象或非构造类型对象能通过二进制拷贝后还保持其数据不变正常就是POD类型的对象。
严格来讲,一个对象既是普通类型(Trivial Type)又是标准布局类型(standard-layout),那么这个对象就是POD类型。
POD对象与c语言中的对象具有一些共同的特性,包括初始化、复制、内存布局与寻址:
- 可以使用字节赋值,比如用 memset、memcpy 对 POD 类型进行赋值操作;
- 对 C 内存布局兼容,POD 类型的数据可以使用 C 函数进行操作且总是安全的;
- 保证了静态初始化的安全有效,静态初始化可以提高性能,如将 POD 类型对象放入 BSS 段默认初始化为 0
参考维基百科:https://en.wikipedia.org/wiki/Passive_data_structure,中描述了说在C++ 20表中:
In C++20 the notion of “plain old data” (POD) and by that is_pod is deprecated and replaced with the concept of “trivial” and “standard-layout” types
不同类型的对象意味着对象的成员在内存中的布局是不同的。在某些情况下,布局是有明确的定义,但如果类或结构包含某些C++语言的功能,如虚拟基类、虚函数
、具有不同的访问控制的成员,则不同的编译器会有不同的布局实现,具体取决于编译器对代码的优化方法,比如实现内存对其,减少访存指令周期。例如,如果类具有
虚函数,该类的所有实例都会包含一个指向虚函数表的指针,那么这个对象就不同直接通过二进制拷贝的方式传到其他语言编程的程序中使用。
C++给定对象的类型取决于其特定的内存布局方式,一个对象是POD、普通和标准布局,可以依据标准库函数模板类判断,使用时需要包含头文件<type_traits>
is_pod<T> is_trivial<T> is_standard_layout<T>
二、普通类型。
当类或结构体同时满足如下几个条件时是普通类型:
(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; }
程序运行结果:
000000001
三、标准布局类型。
当类或结构体同时满足如下几个条件时是标准布局类型:
(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 ; }
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; }
程序运行结果:
:00000001