翻译:怎样理解C++ 11中的trivial和standard-layout---An answer from stackoverflow
上一篇译文中,我们了解到C++中的Aggregates和POD类型,那么在C++ 11中这些定义会有怎样的改变呢,什么是trivial和standard-layout类型呢?相信在这篇译文中,可以找到你想要的答案
-------------------------------------------------------------------------译文
C++ 11 做了哪些改变?
Aggregates
C++标准中Aggregate的定义稍微有所改变,但是之前的定义基本相同:
一个Aggregate是一个数组或者一个没有用户声明构造函数,没有{ }和=直接初始化(brace-or-equal-initializers)的非静态类型成员,没有私有或保护类型的非静态数据成员,没有父类和虚函数的类型
OK,做了哪些改变?
- 之前,aggregate类型不能拥有用户声明的构造函数,现在,不能拥有用户提供的构造函数,有什么区别呢?因为现在你可以声明构造函数并设置为default。这依然是Aggregate类型,因为在声明时任何使用默认实现的构造函数(或者其他特殊成员函数)都不是用户提供的。
1 struct Aggregate { 2 Aggregate() = default; // asks the compiler to generate the default implementation 3 };
- 现在,Aggregate类型不能拥有任何{ }和=直接初始化(brace-or-equal-initializers)的非静态类型成员,什么意思呢?就是因为在新的标准下,我们可以像这样直接声明成员变量:
1 struct NotAggregate { 2 int x = 5; // valid in C++11 3 std::vector<int> s{1,2,3}; // also valid 4 };
Aggregate类型不允许使用这种特性,因为这相当于提供了用户自定义的默认构造函数,所以,什么是Aggregate类型,在新标准中并没有做太多改变,基本概念还是一样的,只不过适用于C++的新特性而已。
PODs呢?
PODs类型有了很多改动。很多之前POD类型的限制在新标准中被放开了,而且,定义的方式也被彻底的改变了。
POD的含义可以从两个显著的特性说明:
- 它支持静态初始化,而且
- 在C++中编译POD类型会和C中编译的struct类型得到相同的内存布局
正是因为这个,这个定义被划分为两个不同的概念:trivial 类型和standard-layout 类型,因为这比POD类型更有用。新标准中已经很少使用POD这个术语了,而是更多的在使用更精确的概念:trival和stand-layout。
新的定义基本说明了POD类型既是trivial类型有事stand-layout,而且,这个特性递归地适用于每个非静态数据成员:
POD struct类型是既为trivial类型又为standard-layout类型,而且还没有非静态类型的non-POD struct和non-POD union(或者这些类型的数组)数据成员的non-union类型。相似地,POD union类型是极为trivial类型尤为standard-layout类型,而且还没有非静态类型的non-POD struct和non-POD union(或者这些类型的数组)数据成员的union类型。POD 类型包含POD struct和POD union这两种类型。
我们来分开、详细说明这两个特性。
Trivial 类型
上面提到的第一个特性Trivial:trivial类型支持静态初始化。如果一个类型是拷贝不变的(trivially copyable),使用memcpy这种方式把它的数据从一个地方拷贝出来会得到相同的结果。
C++标准把trivial类型定义如下:
一个拷贝不变(trivially copyable)类型是指:
-
没有non-trivial 的复制构造函数
-
没有non-trivial的转移构造函数
- 没有non-trivial的赋值操作符
-
没有non-trivial的转移赋值操作符
- 有一个trivial的析构函数
一个trivial class类型是指有一个trivial类型的默认构造函数,而且是拷贝不变的(trivially copyable)的class。(特别注意,拷贝不变类型和trivial类型都不能有虚函数和虚基类)。
那么,这么trivial和non-trivial类型到底是什么呢?
Class X复制或转移构造函数是trivial类型的,如果他不是用户提供的,而且
- Class X没有任何虚函数和虚基类,而且
- 用于复制或转移直接基类的构造函数是trivial类型的,而且
- 复制或转移构造函数选择复制或转移的X内部的每一个非静态数据成员(或数组)必须是trivial类型的
否则,复制或转移构造函数就是non-trivial类型的
从根本上也就是说复制或转移构造函数是trivial类型的只要他不是用户提供的、类内部没有虚函数,而且这个规则要递归地适用于所有数据成员类型和基类。
Trivial类型赋值或转移操作符的定义类似,把构造函数替换为赋值操作符就可以了。
Trivial类型的析构函数也有类似的定义,不过要加一条限制,就是不能为虚函数。
Trivial类型的默认构造函数也需要加一条限制:上面我们已经看到了,不能拥有{ }或=初始化的(brace-or-equal-initializers)非静态数据成员。
这里有个几个例子能让你彻底明白每个类型:
1 // empty classes are trivial 2 struct Trivial1 {}; 3 4 // all special members are implicit 5 struct Trivial2 { 6 int x; 7 }; 8 9 struct Trivial3 : Trivial2 { // base class is trivial 10 Trivial3() = default; // not a user-provided ctor 11 int y; 12 }; 13 14 struct Trivial4 { 15 public: 16 int a; 17 private: // no restrictions on access modifiers 18 int b; 19 }; 20 21 struct Trivial5 { 22 Trivial1 a; 23 Trivial2 b; 24 Trivial3 c; 25 Trivial4 d; 26 }; 27 28 struct Trivial6 { 29 Trivial2 a[23]; 30 }; 31 32 struct Trivial7 { 33 Trivial6 c; 34 void f(); // it's okay to have non-virtual functions 35 }; 36 37 struct Trivial8 { 38 int x; 39 static NonTrivial1 y; // no restrictions on static members 40 } 41 42 struct Trivial9 { 43 Trivial9() = default; // not user-provided 44 // a regular constructor is okay because we still have default ctor 45 Trivial9(int x) : x(x) {}; 46 int x; 47 } 48 49 struct NonTrivial1 : Trivial 3 { 50 virtual f(); // virtual members make non-trivial ctors 51 } 52 53 struct NonTrivial2 { 54 NonTrivial2() : z(42) {} // user-provided ctor 55 int z; 56 } 57 58 struct NonTrivial3 { 59 NonTrivial3(); // user-provided ctor 60 int w; 61 } 62 NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration 63 // still counts as user-provided 64 struct NonTrivial5 { 65 virtual ~NonTrivial5(); // virtual destructors are not trivial 66 };
Standard-layout
Standard-layout是第二个特性,C++标准中说它对语言间交互很有用,这是因为C++ standard-layout类型和C中struct或union类型有相同的内存布局。
这是另一个需要所有成员和基类递归遵循的特性。而且,虚函数和虚基类也是不允许的,这会让内存布局与C不兼容。
这里有一个规定放开了,那就是standard-layout类型的非静态数据成员必须是相同的访问控制,之前他们都必须是public类型的,但是现在他们可以是private或protected类型的了,只要他们都属于同一种。
当使用继承时,在整个继承体系中,只允许一个类拥有非静态数据成员,而且第一个非静态数据成员不能是基类的(这可能打乱量化规则),否则,就不是standard-layout类型。
C++标准中的是这样定义的:
standard-layout 类型的类是指:
- 没有 non-standard-layout类型(或这些类型的数组)和引用的非静态数据成员
-
没有虚函数和虚基类
- 非静态数据成员的访问控制必须是相同的
- 没有non-standard-layout的基类
-
在最底层的派生类中没有非静态数据成员,而且在最多有一个基类拥有非静态数据成员,或者没有拥有非静态数据成员
- 相同基类类型的非静态数据成员不能作为第一个成员
standard-layout类型struct就是以struct或class为关键字定义的standard-layout 类型。
standard-layout类型union就是以union为关键字定义的standard-layout 类型。
[注意:standard-layout类型在C++与其他语言交互时非常重要]
现在我们来看几个例子:
1 // empty classes have standard-layout 2 struct StandardLayout1 {}; 3 4 struct StandardLayout2 { 5 int x; 6 }; 7 8 struct StandardLayout3 { 9 private: // both are private, so it's ok 10 int x; 11 int y; 12 }; 13 14 struct StandardLayout4 : StandardLayout1 { 15 int x; 16 int y; 17 18 void f(); // perfectly fine to have non-virtual functions 19 }; 20 21 struct StandardLayout5 : StandardLayout1 { 22 int x; 23 StandardLayout1 y; // can have members of base type if they're not the first 24 }; 25 26 struct StandardLayout6 : StandardLayout1, StandardLayout5 { 27 // can use multiple inheritance as long only 28 // one class in the hierarchy has non-static data members 29 }; 30 31 struct StandardLayout7 { 32 int x; 33 int y; 34 StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok 35 }; 36 37 struct StandardLayout8 { 38 public: 39 StandardLayout8(int x) : x(x) {} // user-provided ctors are ok 40 // ok to have non-static data members and other members with different access 41 private: 42 int x; 43 }; 44 45 struct StandardLayout9 { 46 int x; 47 static NonStandardLayout1 y; // no restrictions on static members 48 }; 49 50 struct NonStandardLayout1 { 51 virtual f(); // cannot have virtual functions 52 }; 53 54 struct NonStandardLayout2 { 55 NonStandardLayout1 X; // has non-standard-layout member 56 }; 57 58 struct NonStandardLayout3 : StandardLayout1 { 59 StandardLayout1 x; // first member cannot be of the same type as base 60 }; 61 62 struct NonStandardLayout4 : StandardLayout3 { 63 int z; // more than one class has non-static data members 64 }; 65 66 struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class
结论:
在新的标准下,很多新类型成为POD类型,而且,就算一个类型不是POD类型,我们也可以分别利用POD类型的特性(只要这个类型是trivial或者standard-layout)。
标准模板块(STL)在头文件<type_traits>中定义了对这些类型的检测:
1 template <typename T> 2 struct std::is_pod; 3 template <typename T> 4 struct std::is_trivial; 5 template <typename T> 6 struct std::is_trivially_copyable; 7 template <typename T> 8 struct std::is_standard_layout;