trivial、standard layout、POD和literal类型解析

参考资料

microsoft_trivial-standard-layout-and-pod-types
stackoverflow_trivial-vs-standard-layout-vs-pod
When is a type in c++11 allowed to be memcpyed?
is_pod vs is_trivial vs is_trivially_copyable vs is_standard_layout

关于layout

单词layout用于表示class、struct或union对象在内存中的分布,有些对象是连续占用内存区间的,而对于下面几种情况下对应的类对象,the compiler is free to choose a layout:

  • 拥有虚基类的类
  • 有虚函数的类
  • 有不同访问权限数据成员的类(members with different access control)

这些类对象里面的内存可能不是连续分配的,所以不能使用memcpymemmove类似的函数,也不可以serialized over a network

自C++11起,推出了三种traits,可以用来访问structs和classes的相关属性,分别是:

  • is_trivial<T>
  • is_standard_layout<T>
  • is_pod<T>(C++20开始废弃)
  • is_literal<T>(C++17废弃,C++20移除)


下面分别介绍这四种类型的区别:

Trivial Type

When a class or struct in C++ has compiler-provided or explicitly defaulted special member functions, then it is a trivial type. Trivial types have a trivial default constructor, trivial copy constructor, trivial copy assignment operator and trivial destructor.

一个类,如果它的默认构造函数、拷贝构造函数、拷贝运算符重载和析构函数都是trivial函数,那它就是Trivial类。

那么如何判断一个函数是否为trivial函数呢,trivial函数需要满足以下几点:

  • 函数是编译器自动生成的
  • 只可以是六种类型的函数:default ctor、copy ctor、dtor、copy assignment operator、move ctor和move assignment operator
  • 函数所在的类里没有虚函数或虚基类
  • 如果函数所在类继承于一个基类,该基类相关的函数也必须是trivial函数
  • 函数所在的类的类成员,也需要是trivial类

总结来说,如果一个类的上述四个函数都是编译器自动生成的,而且该类对应的基类和所有的成员都是trivial类型,且没有虚函数,则该类为trivial type。

参考代码如下:

#include<type_traits>
struct Trivial
{
	int i;
private:
	int j;// 注意,i和j的access不同,所以类Trivial是Trivial类,但不是standard layout类
};

struct Trivial2
{
	int i;
	Trivial2(int a, int b) : i(a), j(b) {}
	Trivial2() = default;
private:
	int j;   // Different access control
};

struct NotTrivial
{
	int a;
	NotTrivial() :a(0) {};// 自定义ctor会让编译器不再生成default ctor
};

struct NotTrivial2
{
	int a;
	virtual void Func() {}
};
int main()
{
	cout << std::is_trivial<Trivial>() << endl;		// print 1
	cout << std::is_trivial<Trivial2>() << endl;	// print 1
	cout << std::is_trivial<NotTrivial>() << endl;	// print 0
	cout << std::is_trivial<NotTrivial2>() << endl;	// print 0
}

trivial函数
trivial函数会影响以下方面(出自C++ Concurrency In Action(Second Edition) P361):

  1. 对于principle of three里的三个函数: 只有copy ctor、copy assignment operator和dtor都为trivial函数的类对应的对象,才可以用memcpymemmove来进行对象的复制
  2. 用于constexpr函数的Literal Types,需要有trivial copy ctor、trivial copy assignment operator和trivial dtor函数
  3. 如果一个类,其default ctor、copy assignment operator、copy ctor和dtor都为trivial函数,则该class can be used in a union with a user-defined ctor and dtor
  4. 如果一个类,定义了trivial copy assignment operator,则该类可以用于std::atomic<>从而提供a vlue of that type with atomic operations

Standard Layout

在layout部分里已经介绍过了,如果一个类是Standard Layout类,那么该类的对象在内存上的分配是连续的。

standard layout类应该具有以下特点:

  • 没有虚函数或者虚基类
  • 所有的非static的data members都是相同的访问级别
  • 所有的非static的data members都是standard layout类的对象
  • 如果有基类,基类也是standard layout
  • has no base classes of the same type as the first non-static data member.
  • 满足以下两个条件之一:
    (一)派生类没有非static成员,基类有,但又不可以同时继承于多个拥有non-static数据成员的基类
    (二)如果继承于一个基类,则该基类不可有非static的data members

举个例子:

// 虽然SL不是trivial类,但却是standard layout类
struct SL
{
   // All members have same access:
   int i;
   int j;
   SL(int a, int b) : i(a), j(b) {} // User-defined constructor OK
};

当继承的基类也有非static数据成员时,该类不是standard layout类:

struct Base
{
   int i;
   int j;
};

// Derived类不是standard layout类, std::is_standard_layout<<Derived> == false!
struct Derived : public Base
{
   int x;
   int y;
};

struct Base2
{
   void Foo() {}
};

// std::is_standard_layout<<Derived2> == true
struct Derived2 : public Base2
{
   int x;
   int y;
};

POD types

When a class or struct is both trivial and standard-layout, it is a POD (Plain Old Data) type. The memory layout of POD types is therefore contiguous and each member has a higher address than the member that was declared before it, so that byte for byte copies and binary I/O can be performed on these types. Scalar types such as int are also POD types. POD types that are classes can have only POD types as non-static data members.

如果一个class或stuct,既是trivial类,也是standard layout类,那么该类是POD类。

POD类是老版的C语言对类型的形容,实际上,它可以分为两个部分,trivial和standard layout,所以新的C++版本把POD进行了进一步细分,在C++20开始,废弃了is_pod<T>对应的trait

对于POD类T,new T() is value-initialization(will value-initialize all members) ,and new T will not initialize the members (default-initialization).

举个代码的例子:

#include <type_traits>
#include <iostream>

using namespace std;

struct B
{
protected:
   virtual void Foo() {}
};

// A继承于一个有虚函数的基类,所以A既不是trivial也不是standard layout
struct A : B
{
   int a;
   int b;
   void Foo() override {} // Virtual function
};

// Trivial but not standard-layout
struct C
{
   int a;
private:
   int b;   // Different access control,成员访问权限不同
};

// Standard-layout but not trivial
struct D
{
   int a;
   int b;
   D() {} //User-defined constructor, 有自定义的default ctor
};

struct POD
{
   int a;
   int b;
};

int main()
{
   cout << boolalpha;
   cout << "A is trivial is " << is_trivial<A>() << endl; // false
   cout << "A is standard-layout is " << is_standard_layout<A>() << endl;  // false

   cout << "C is trivial is " << is_trivial<C>() << endl; // true
   cout << "C is standard-layout is " << is_standard_layout<C>() << endl;  // false

   cout << "D is trivial is " << is_trivial<D>() << endl;  // false
   cout << "D is standard-layout is " << is_standard_layout<D>() << endl; // true

   cout << "POD is trivial is " << is_trivial<POD>() << endl; // true
   cout << "POD is standard-layout is " << is_standard_layout<POD>() << endl; // true

   return 0;
}

Literal types

还有一个trait,叫is_literal<T>,literal类型的类,其layout可以在Compile Time确定,具体有如下类型:

  • void
  • scalar types,比如int, double等
  • references
  • Arrays of void, scalar types or references
  • A class that has a trivial destructor, and one or more constexpr constructors that are not move or copy constructors. Additionally, all its non-static data members and base classes must be literal types and not volatile.

四种类型的类的关系

POD、standard layout、trivial和literal type的关系应该是这样(我自己画的,不一定准确):
在这里插入图片描述


如何判断一个类的对象是否可以用memcpy来复制

根据前面的介绍,应该是需要该类为standard layout类,而查阅资料后发现,其实只要is_trivially_copyable<T>::value返回true,即可,即使不满足standard layout,也可以使用memcpy,比如下面的类:

struct T {
  int i;
private:
  int j;
};

也就是说,不用满足standard layout的类,也可以使用memcpy和memcopy,在C++ Concurrency In Action(Second Edition)里提到:

Objects with trivial copy constructors, trivial copy assignment operators, and trivial destructors can be copied with memcpy or memmove

至于std::is_standard_layout,则一般用于保证C++与其他语言(比如C语言)的内存分布一致。

std::is_standard_layou is useful for communicating with other languages (for creating language bindings to native C++ libraries e.g.), and that’s why a standard-layout class has the same memory layout of the equivalent C struct or union.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值