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 NotAggregate312 {13 public:14 NotAggregate3(int) {} //oops, user-defined constructor15 };16 17 class Aggregate118 {19 public:20 NotAggregate1 member1; //ok, public member21 Aggregate1& operator = (Aggregate1 const & rhs) {/* */} //ok, copy-assignment 22 private:23 void f() {} // ok, just a private function24 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 == m12 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}; //所有元素被初始化為015 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 AggregateButNotPOD110 {11 int x;12 ~AggregateButNotPOD1(){} //user-defined destructor13 };14 15 struct AggregateButNotPOD216 {17 AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class18 };
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 value4 memcpy(buf, &obj, N); // between these two calls to memcpy,5 // obj might be modified6 memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type7 // 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++語言特性,就像你看到的,針對於他們都會有所不同。希望這篇文章對你有用。