1. trivial类型
占用一片连续的内存,编译器可以重排成员变量的顺序或者增加一些padding(为了对齐),因此,可以放心的使用memcpy等函数,
但是,在c代码里面使用可能会出问题(因为可能会被重排),有如下特点:
- 没有虚函数和虚基类
- 基类也必须保证没有non-trivial的构造/析构/operator函数
- 成员变量的类型也必须保证没有non-trivial的构造/析构/operator函数
- 成员变量可以有public/protected/private修饰,且可以出现多个。
- 构造/operator/析构可以有自定义的,但是必须有默认的
可以看出来,trivial主要是针对于 构造/operator/析构 三种函数限制的,普通函数只要不是虚函数,就没有任何限制。
题外话,关于重排序,来看一个例子:
struct Foo { A a; B b; C c; private: D d; E e; F f; };
一般来讲,abc的顺序是不会重排的,def的顺序是不会重排的,但是,因为abc和def拥有着不同的访问控制符,是有可能def重排到abc前面的。官方解释
如下:
Nonstatic data members of a (non-union) class declared without an intervening access-specifier are allocated
so that later members have higher addresses within a class object.
The order of allocation of nonstatic data members separated by an access-specifier is unspecified (11.1)
注意,静态成员变量不影响
好了,定义部分讲完了,来看几个例子:
//trivial类型,同时也是pod类型(后面会解释) class TrivialClass{ int a_; bool flag_; //bool flag2_{false}; //这种就不是trivial了 }; //不是trivial类型 class NonTrivialClass{ int a_; bool flag_; bool flag2_{false}; //比上面就多了一个初始化赋值,就不是trivial了,但是这个是stdlayout(后续会介绍) }; class NonTrivialClass2{ int a_; bool flag_; bool flag2_ = false; //同NonTrivialClass }; //不是trivial类型 class NonTrivialClass3{ NonTrivialClass3() {} //和NonTrivialClass3() = default的写法是不一样的,决定是否为trivial类型 int a_; bool flag_; bool flag2_ = false; //同NonTrivialClass }; //下面的也满足trivial要求 class TrivialClass2{ public: //注意,静态类型是不影响判断的,因为静态成员变量不会存储在结构体/类里面 static std::string static_member; //也可以有构造函数,但是必须有默认的构造函数才行 TrivialClass2(int a, int b): a_(a), b_(b) {} TrivialClass2() = default; void foo() {} int a_; private: int b_; bool flag_; };
虽然举的例子都是没有继承的简单类,但是如果有继承,也有可能是trivial类型,需要保证基类也是trivial类型
2. standard layout类型
standard layout类型(以下简称stdlayout):不包含任何c语言里没有的功能,可以使用memcpy等函数,并且可以在c代码里放心使用,这点不同于trvial的类型,同时,stdlayout类型也能拥有自己的函数,有如下特点:
- 没有虚函数和虚基类
- 所有的非静态成员变量必须拥有完全一样的public/protected/private访问控制符, 比如下面的
TrivialClass2就不是stdlayout类型 - 所有非静态成员变量也必须是stdlayout类型
- 所有的基类也必须是stdlayout
- 不能把基类类型作为第一个非静态成员变量
- 下面的条件满足一个即可(总结就是要么子类有非静态成员变量,要么父类有):
- 子类没有非静态成员变量并且只有一个基类有非静态成员变量
- 基类都没有非静态成员变量
注意,静态成员变量不影响
可以看出来,stdlayout放宽了构造/operator/析构的限制,但是也收紧了访问控制符的限制(只能有一种)
接下来看代码示例:
//满足stdlayout的要求,但是因为没有默认的构造函数,因此不满足trivial的要求 class StdLayoutClass{ public: StdLayoutClass(int a, int b): a_(a), b_(b) {} int a_; int b_; }; //和上面的结构体就多了一个private(权限修饰符不一样),这里就不是stdlayout了 class ClassMixedAccess{ public: ClassMixedAccess(int a, int b): a_(a), b_(b) {} int a_; private: int b_; }; class StdLayoutBase{ public: StdLayoutBase() {} //和StdLayoutBase()=default的写法是有区别的,决定了是否为trivial int a_; int b_; }; //这个不是standard layout,因为基类有非静态成员变量,也不是trivial,因为积累的构造函数不是default(注意,和=default的写法是有区别的) class StdLayoutDerived : public StdLayoutBase { int c_; int d_; }; class StdLayoutBase2{ StdLayoutBase2() = default; }; //这个就满足stdlayout的要求了,因为必须满足子类或者基类只能有一个有非静态成员变量 class StdLayoutDerived2 : public StdLayoutBase2 { StdLayoutDerived2() = default; int c_; int d_; //可以拥有自己的函数 void test_func(){ printf("hello world"); } };
3. 集大成者,POD(Plain Old Data)类型
POD类型:当一个类既是trivial又是stdlayout类型的时候,这个时候它就是pod类型,因此,pod类型的特点如下:
- 拥有着连续的内存
- 每一个成员变量的地址都高于其前一个成员变量(也就是没有进行重排序操作)
- 同样的,也有嵌套要求,非静态成员变量必须也是POD类型
综上,POD类型可以在I/O操作中放心的进行copy和恢复, 基本类型比如int,char,引用等都是POD类型。
注意,静态成员变量不影响。
示例代码如下:
class ClassWithVirtual{ public: virtual bool foo() {} }; //出现了虚函数,因此,trivial和stdlayout都不是 class ClassWithVirtualDerived : public ClassWithVirtual { public: int a_; int b_; virtual bool foo() override{} }; //有不同的访问限定符,因此,属于trivial但是不属于stdlayout class ClassDiffAccess{ public: int a_; private: int b_; }; //拥有着自定义的构造函数(但是没有显示指定default),因此不属于trivial class ClassWithoutDefaultConstructor{ public: ClassWithoutDefaultConstructor() {}; int a_; int b_; }; //集大成者,既属于trivial也属于stdlayout,也就是POD类型 class PODClass { int a_; int b_; };
4. 测试代码
上述类,可以通过如下代码测试
#define BOOL_2_STRING(val) ((val) ? "true" : "false") /** * tool function * @tparam T */ template<class T> void test_trivial_stdlayout_pod_type_internal(const char *type_name) { bool is_trivial = std::is_trivial<T>::value; bool is_std_layout = std::is_standard_layout<T>::value; bool is_pod = std::is_pod<T>::value; printf("%s: is_trivial[%s] is_standard_layout[%s] is_pod[%s]n", type_name, BOOL_2_STRING(is_trivial), BOOL_2_STRING(is_std_layout), BOOL_2_STRING(is_pod)); } /** * 测试三大类型,trivial,standard layout, pod */ void test_trivial_stdlayout_pod_type() { test_trivial_stdlayout_pod_type_internal<TrivialClass>("TrivialClass"); test_trivial_stdlayout_pod_type_internal<NonTrivialClass>("NonTrivialClass"); test_trivial_stdlayout_pod_type_internal<NonTrivialClass2>("NonTrivialClass2"); test_trivial_stdlayout_pod_type_internal<NonTrivialClass3>("NonTrivialClass3"); test_trivial_stdlayout_pod_type_internal<TrivialClass2>("TrivialClass2"); test_trivial_stdlayout_pod_type_internal<StdLayoutClass>("StdLayoutClass"); test_trivial_stdlayout_pod_type_internal<ClassMixedAccess>("ClassMixedAccess"); test_trivial_stdlayout_pod_type_internal<StdLayoutDerived>("StdLayoutDerived"); test_trivial_stdlayout_pod_type_internal<StdLayoutDerived2>("StdLayoutDerived2"); test_trivial_stdlayout_pod_type_internal<ClassWithVirtualDerived>("ClassWithVirtualDerived"); test_trivial_stdlayout_pod_type_internal<ClassDiffAccess>("ClassDiffAccess"); test_trivial_stdlayout_pod_type_internal<ClassWithoutDefaultConstructor>("ClassWithoutDefaultConstructor"); test_trivial_stdlayout_pod_type_internal<PODClass>("PODClass"); } /* 输出如下: TrivialClass: is_trivial[true] is_standard_layout[true] is_pod[true] NonTrivialClass: is_trivial[false] is_standard_layout[true] is_pod[false] NonTrivialClass2: is_trivial[false] is_standard_layout[true] is_pod[false] NonTrivialClass3: is_trivial[false] is_standard_layout[true] is_pod[false] TrivialClass2: is_trivial[true] is_standard_layout[false] is_pod[false] StdLayoutClass: is_trivial[false] is_standard_layout[true] is_pod[false] ClassMixedAccess: is_trivial[false] is_standard_layout[false] is_pod[false] StdLayoutDerived: is_trivial[false] is_standard_layout[false] is_pod[false] StdLayoutDerived2: is_trivial[true] is_standard_layout[true] is_pod[true] ClassWithVirtualDerived: is_trivial[false] is_standard_layout[false] is_pod[false] ClassDiffAccess: is_trivial[true] is_standard_layout[false] is_pod[false] ClassWithoutDefaultConstructor: is_trivial[false] is_standard_layout[true] is_pod[false] PODClass: is_trivial[true] is_standard_layout[true] is_pod[true] */