布局
布局指的是类、结构或者联合体的对象的成员在内存中的排列方式
-
某些情况下,布局由语言规范明确定义
-
但是,当类或者结构包含某些C++语言功能(比如虚拟基类、虚拟函数、具有不同访问控制的成员)时,编译器可以自由选择布局。
-
该布局可能会基于正在执行的优化而有所不同,并且在很多情况下,该对象甚至可能不会占用连续内存区域。
- 例如:如果某个类具有虚拟函数,则该类的所有实例可能会共享单个虚拟函数表。此类型非常有用,但它们也有限制。由于布局未定义,因此无法将其传递到使用其他语言(比如C)编写的程序,并且由于它们可能是非连续的,因此无法使用快速低级函数(比如memcopy)对其进行可靠赋值,或者通过网络对其进行序列化
-
为使编译器以及C++程序和元程序能够推断出任何给定类型对于依赖于特定内存布局的操作的适用性,C++14引入了三种类别的简单类和结构:普通、标准布局和POD(简单旧数据)。 标准库具有函数模板 is_trivial、is_standard_layout 和 is_pod,这些模板可以确定某一给定类型是否属于某一给定类别。
普通类型
- 当C++中的类或结构具有编译器提供的或者显示默认设置的特殊成员函数时,该类或者结构为普通类型。
- 它占用连续的内存区域。
- 可具有不同访问说明符的成员。在C++中,编译器可以自由选择在此情况下对成员排序的方式。因此,你可以在内存中复制此对象,但是不能从C程序中可靠的使用它们。
- 可以将普通连续T复制到char或者unsigned char数组,并安全的复制回T变量
- 请注意,由于对齐要求,类型成员之间可能存在填充字节
普通类型具有普通默认构造函数、普通复制构造函数、普通1复制赋值运算符、普通析构函数。在各种情况下,”普通“意味着构造函数/运算符、析构函数并非由用户提供,并且存在以下情况的类:
- 没有虚拟函数或者虚拟基类
- 没有具有相应非普通构造函数/运算符/析构函数的基类
- 没有具有相应非普通构造函数/运算符/析构哈数的类类型的数据成员
以下示例演示普通类型。在Trivial2中,Trivial2(int a, int b)
构造函数的存在要求提供默认构造函数。对于符合普通资源的类型,必须显式默认设置该构造函数:
#include <vector>
#include <iostream>
struct Trivial{
int i;
private:
int j; // true
};
struct Trivial2
{
int i;
Trivial2(int a, int b) : i(a), j(b) {
}
Trivial2() = default;
private:
int j; // Different access control
};
int main()
{
std::cout << std::boolalpha << std::is_trivial<Trivial>::value << "\t";
std::cout << std::boolalpha << std::is_trivial<Trivial2>::value << "\t";
}
标准布局类型
- 当类或者结构不保证某些C++语言功能(例如无法在C语言中找到的虚拟函数),并且所有成员都具有相同的访问控制,该类或者结构为标准布局类型
- 可以在内存中对其进行复制,并且布局已经过充分定义,可以由C程序使用。
- 标准局部类型可以具有用户定义的特殊成员函数
此外,标准布局类型还具有以下特征:
- 没有虚拟函数或者虚拟基类
- 所有非静态数据成员都具有相同的访问控制
- 类类型的所有非静态成员均为标准布局
- 所有基类都为标准布局
- 没有与第一个非静态数据成员相同的基类
- 满足以下条件之一:
- 最底层派生类中没有非静态数据成员,并且具有非静态数据成员的基类不超过一个,或者
- 没有含非静态数据成员的基类
以下代码演示标准布局类型的一个示例:
struct SL
{
// All members have same access:
int i;
int j;
SL(int a, int b) : i(a), j(b) {
} // User-defined constructor OK
};
可能使用代码能够更好地说明最后两个要求。 在下一个示例中,即使 Base 是标准布局,Derived 也不是标准布局,因为它(最底层派生类)和 Base 都具有非静态数据成员:
struct Base
{
int i;
int j;
}; // std::is_standard_layout<<Base> == true!
// std::is_standard_layout<<Derived> == false!
struct Derived : public Base
{
int x;
int y;
};
POD类型
- 当某一类或结构即是普通类型又是标准布局时,该类或者结构为POD[简单旧数据]类型
- 因此,POD类型的**内存布局是连续的,并且每个成员的地址都比在其之前声明的成员要高,**以便可以对这些连续执行逐字节复制和二进制IO。
- 标量类型(例如 int)也是 POD 类型。
- 作为类的 POD 类型只能具有作为非静态数据成员的 POD 类型。
#include <type_traits>
#include <iostream>
using namespace std;
struct B
{
protected:
virtual void Foo() {
}
}; // 有虚拟函数,所以它不是普通(平凡)类型,也不是标准类型,更不是POD类型
// Neither trivial nor standard-layout
struct A : B
{
int a;
int b;
void Foo() override {
} // Virtual function
}; // 继承虚拟基类,所以他不是普通(平凡)类型,也不是标准布局类型,更不是POD类型
// 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
}; // 用户自定义构造函数,所以它不普通(平凡)。但是他是标准布局
struct POD
{
int a;
int b;
};
int main()
{
cout << boolalpha;
cout << "B is trivial is " << is_trivial<B>() << endl; // false
cout << "B is standard-layout is " << is_standard_layout<B>() << endl; // false
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;
}
文本类型
文本类型是可在编译时确定其布局的类型。以下均为文本类型:
- void
- 标量类型
- 引用
- void、标量类型或者引用的数组
- 具有普通析构函数以及一个或多个 constexpr 构造函数且不移动或复制构造函数的类。 此外,其所有非静态数据成员和基类必须是文本类型且不可变。