上一篇譯文中,我們了解到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;