翻译:怎样理解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,做了哪些改变?

  1. 之前,aggregate类型不能拥有用户声明的构造函数,现在,不能拥有用户提供的构造函数,有什么区别呢?因为现在你可以声明构造函数并设置为default。这依然是Aggregate类型,因为在声明时任何使用默认实现的构造函数(或者其他特殊成员函数)都不是用户提供的。
    1 struct Aggregate {
    2     Aggregate() = default; // asks the compiler to generate the default implementation
    3 };

     

  2. 现在,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的含义可以从两个显著的特性说明:

  1. 它支持静态初始化,而且
  2. 在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;

 

posted on 2013-03-28 10:48  落叶的思维  阅读(6742)  评论(5编辑  收藏  举报

导航