翻譯:怎樣理解C++中的Aggregate和POD類型---An answer from stackoverflow

本文轉載自:http://www.cnblogs.com/tingshuo/archive/2013/03/25/2981197.html

C++ 11標準中統一了初始化語法,在瞭解這些變化之前,我們有必要對Aggregate類型和POD類型有所瞭解,看到stack overflow上有篇不錯的文章(原文),對Aggregate、POD和C++ 11中的變化有詳盡的解釋,感覺非常不錯,先翻譯前半部分,後半部分過兩天再給出。

-------------------------------------------------------------------------譯文

如何來讀:

這篇文章很長,如果Aggregates和PODs都想了解,就靜下心來完整的把這篇文章讀完,如果你僅僅對Aggregates感興趣,讀第一部分就可以了。如果你僅對PODs感興趣,那你必須先讀懂Aggregates的定義、含義和例子,然後再跳去讀PODs,但是我依然推薦你完整的讀完第一部分。Aggragates的概念是定義PODs的基礎。

什麼是Aggragates,爲什麼他們這麼特別?

C++標準(C++ 03 8.5.1 §1)中的正式定義:

一個Aggregate是一個數組或者一個沒有用戶聲明構造函數,沒有私有或保護類型的非靜態數據成員,沒有父類和虛函數的類型 

現在我們來分析這個定義。首先,數組是Aggregate。class也可以成爲Aggregate如果滿足…等等!我們還沒有說struct和unions,它們可以成爲Aggregate嗎?是的,他們可以。在C++中,術語class是指所有的classes、structs和unios。所以,class(struct,union)只要滿足上面定義中的條件就可以成爲Aggregate。這些條件有什麼含義呢?

  • 這並不是說Aggregate類型就不能有構造函數,事實上,它可以擁有一個默認構造函數或者一個複製構造函數,只要他們是被編譯器聲明的,而不是被用戶自己聲明的。
  • 不能擁有私有或者保護類型的非靜態數據成員。你可以定義任意多的私有或者保護類型的成員方法(不包括構造函數)和靜態類型的數據成員和方法,這都不違背Aggregate類型的規則。
  •  Aggregate類型可以擁有用戶聲明的/用戶定義的 賦值操作符或者析構函數
  • 數組是Aggregate類型,即便是非Aggregate類型元素的數組。

 來看幾個例子:

複製代碼
 1 class NotAggregate1
 2 {
 3  virtual void f(){} //remember? no virtual functions
 4 };
 5 
 6 class NotAggregate2
 7 {
 8  int x; //x is private by default and non-static 
 9 };
10 
11 class NotAggregate3
12 {
13 public:
14     NotAggregate3(int) {} //oops, user-defined constructor
15 };
16 
17 class Aggregate1
18 {
19 public:
20     NotAggregate1 member1;   //ok, public member
21     Aggregate1& operator = (Aggregate1 const & rhs) {/* */} //ok, copy-assignment  
22 private:
23    void f() {} // ok, just a private function
24 
25 };
複製代碼

你已經理解了Aggregates含義了,現在我們來看爲什麼它這麼特別。他們和非Aggregates類型不同,可以使用“{ }”初始化。這種初始化語法,在數組上很常見,而且,我們剛剛瞭解到數據就是Aggregates類型,所以,我們從數組開始:

Type array_name[n] = {a1, a2, ..., am};

if(m == n) 

數組的第i個元素被初始化爲ai

else if(m < n)

數組前邊的m個元素被初始化爲a1, a2, ..., am,剩餘的n-m個元素,如果可能,將按值初始化(下面有關於這個名詞的解釋)

else if(m > n)

會引起編譯錯誤

else(有可能爲這種形式a[] = {1,2,3};

數組的長度將被推測爲m,所以int a[] = {1,2,3}等於a[3] = {1,2,3} 

標量類型的(bool,int,char,double,指針)對象是按值初始化(value-initialized)的,意思是指它被初始化爲 0 (bool類型被初始化爲false, double被初始化爲0.0,等等)。有用戶聲明的默認構造函數的Class類型的對象按值初始化時,他的默認構造函數就會被調用。如果默認構造函數是被隱式定義的,那麼所有的非靜態類型成員變量將會遞歸地按值初始化。雖然這個定義並不精確,也不完全正確,但是可以讓你有個基本的認識。最近我將會寫一篇關於zero-initialization,value-initialization和default-initialization之間區別的文章。引用不能按值初始化。對於非Aggregate類型的class進行按值初始化有可能失敗,比如在沒有合適的默認構造函數的情形下。 

數組初始化的例子:

複製代碼
 1 class A()
 2 {
 3    A(int){} //no default constructor
 4 };
 5 class B()
 6 {
 7    B() {} //default constructor available
 8 };
 9 int main()
10 {
11   A a1[3] = {A(2), A(1), A(14)}; //OK n == m
12   A a2[3] = {A(2)}; //ERROR A沒有默認構造函數. 不能按值初始化a2[1] 和 a2[2]
13   B b1[3] = {B()}; //OK b1[1]和b1[2]使用默認構造函數按值初始化
14   int Array1[1000] = {0}; //所有元素被初始化爲0
15   int Array2[1000] = {1}; //注意: 只有第一個元素被初始化爲1,其他爲0;
16   bool Array3[1000] = {}; //大括號裏可以爲空,所有元素被初始化爲false;
17   int Array4[1000]; //沒有被初始化. 這和空{}初始化不同;
18   //這種情形下的元素沒有按值初始化,他們的值是未知的,不確定的; 
19   //(除非Array4是全局數據)
20   int array[2] = {1,2,3,4}; //ERROR, 太多初始值
複製代碼

現在我們來看Aggregates類型是如何使用{ }初始化的。和上面非常類似,按照在類內部聲明的順序(按照定義都必須是public類型)初始化非靜態類型的成員變量。如果初始值比成員少,那麼其他的成員將按值初始化。如果有一個成員無法進行按值初始化,我們將會得到一個編譯期錯誤。如果初始值比成員多,我們同樣得到一個編譯期錯誤。

複製代碼
 1 struct X{
 2     int i1;
 3  int i2;
 4 };
 5 struct Y{
 6     char c;
 7  X x;
 8  int i[2];
 9     float f; 
10 protected:
11  static double d;
12 private:
13     void g(){}      
14 }; 
15 
16 Y y = {'a', {10,20}, {20,30}};
複製代碼

上面的例子中,y.c被初始化爲’a’,y.x.i1被初始化爲10,y.x.i2被初始化爲20,y.i[0]爲20,y.i[1]爲30,y.f被按值初始化,也即是說,被初始化爲0.0,保護類型的靜態成員變量d不會被初始化,因爲它是靜態類型的。 

Aggregate類型的unions有所不同,使用{ }你可能只能初始化它們的第一個成員,我想如果你使用C++高級到考慮使用unions(使用他們非常危險,必須小心謹慎),你一定可以自己在C++標準中找到unions的規則。 

我們知道了Aggregates的特別之處,現在讓我們來嘗試理解一下它對類型的限制,也就是說爲什麼會有這些限制。我們應當理解使用{ }進行成員逐一初始化意味着這一類型只是成員的集合。如果有一個用戶定義的構造函數,那意味着用戶需要做一些額外的工作來初始化成員,因此使用{ }初始化是不正確的。如果出現了虛函數,那意味着這個類型(大多數實現)有一個指向vtable的指針,需要在構造函數內設置,所以使用{ }初始化是不夠的。作爲練習,你可以按照這種方式自己理解其他限制的含義。

關於Aggregates的就這麼多,現在我們可以更嚴格定義一個子類型PODs 

什麼是PODs,爲什麼他們這麼特別

C++標準(C++ 03 9 §4)中正式的定義爲:

POD-struct類型是沒有非靜態類型的non-POD-struct,non-POD-union (或者這些類型的數組)和引用類型的數據成員,也沒有用戶定義的賦值操作符和析構函數的Aggregate類型的類。類似地,POD-union是沒有非靜態類型的non-POD-struct,non-POD-union (或者這些類型的數組)和引用類型的數據成員,也沒有用戶定義的賦值操作符和析構函數的Aggregate類型的聯合。POD類型就是POD-struct和 a POD-union中的一種。 

Wow,這個定義更難解讀,不是嗎?讓我們吧unions剝離出去,更清晰的複述爲:

POD類型就是沒有非靜態類型的non-POD類型 (或者這些類型的數組)和引用類型的數據成員,也沒有用戶定義的賦值操作符和析構函數的Aggregate類型。

 這個定義的有什麼含義呢?(POD就是Plain Old Data)

  • 所有的POD類型都是Aggregates類型,換句話說,如果不是aggregate類型,那麼它一定不是POD類型。
  • 類,和結構體一樣可以爲POD類型,因爲標準中POD-struct這個術語包含了這兩種情形。
  • 和Aggregates類型一樣,靜態成員是什麼類型則無關緊要

例子:

複製代碼
 1 struct POD
 2 {
 3  int x;
 4  char y;
 5  void f() {} //no harm if there's a function
 6  static std::vector<char> v; //static members do not matter
 7 };
 8 
 9 struct AggregateButNotPOD1
10 {
11  int x;
12  ~AggregateButNotPOD1(){} //user-defined destructor
13 };
14 
15 struct AggregateButNotPOD2
16 {
17  AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
18 };
複製代碼

POD-classes,POD-unions,標量類型和這些類型的數組合成爲POD類型,POD類型在很多方面都很特別,我來舉幾個例子:

  • POD類型是最接近於C語言中的結構體類型的。他們都沒有改變對象的內存佈局,但是,POD類型卻可以有自己的成員函數和任意類型的靜態成員。所以,如果你想寫一個可在C甚至.net平臺使用的可移植的動態庫,你應該讓暴露的所有的方法的返回值和參數都會POD類型。
  • 非POD類型的對象的生命週期起始於構造函數,結束於析構函數調用完成。而POD類型對象的生命週期卻起始於存儲對象的空間被佔用,結束於空間被釋放或被重複利用。
  • 對於POD類型的對象,C++標準保證當你使用memcpy將對象的內容拷貝到一個char類型或者unsigned char類型的數組中,在使用memcpy拷貝回來的時候,對象會保持不變。特別注意,非POD類型是無法保證這一點的。當然,你也可以安全的在對象之間拷貝POD類型。下面的這個例子假設T爲POD類型
複製代碼
1 #define N sizeof(T)
2 char buf[N];
3 T obj; // obj initialized to its original value
4 memcpy(buf, &obj, N); // between these two calls to memcpy,
5 // obj might be modified
6 memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
7 // holds its original value
複製代碼
  • goto 語句。你知道,使用goto從一個變量沒有聲明的點跳轉到一個變量已經被聲明的點是不合法的(編譯器應該會有報錯)。這個限制僅僅對非POD類型有效,下面這個例子f()是不合法的,而g()則是合法的。注意到微軟的編譯器對這條規則過於慷慨了,僅僅給出警告而已。

    複製代碼
     1 int f() {
     2   struct NonPOD { NonPOD(){}};
     3   goto label;
     4   NonPOD x;
     5 label:
     6   return 0;
     7 }
     8 
     9 int g(){
    10   struct POD {int i;  char c;};
    11   goto label;
    12   POD x;
    13 label:
    14   return 0;
    15 }
    複製代碼

     

  • C++標準保證POD類型的對象在內存起始處沒有便宜。也就是說如果一個POD類型A的第一個成員爲T,你可以安全的調用reinterpret_cast  從A*轉換爲T*,得到第一個成員的指針,反過來也成立。

這個列表還很長很長…

結論

理解POD類型非常重要,因爲很多C++語言特性,就像你看到的,針對於他們都會有所不同。希望這篇文章對你有用。


發佈了50 篇原創文章 · 獲贊 7 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章