翻譯:怎樣理解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;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM