c++標準中有這樣一個規定:
"no object shall have thesame address in memory as any other variable".
由此規定,導致幾乎所有的編譯器對一個空類都會添加一個char。
那什麼是一個空類呢:
·Has static members /member function
·Has no virtual function
這樣的類就是一個空類。
由於編譯器對每一個空類都會添加一個char,來保證每一個class對象都具有不同的地址 ,那麼在不同的繼承環境下上就會導致一些問題。
我們主要講兩個一個是空類的單一繼承,另外一個是空類的虛擬繼承:
對於第一種情況,我們用一個空類去繼承另外一個空類。
我們已知Empty_A大小爲一個字節,那麼根據vs的編譯結果來看Empty_B大小爲四個字節。
對於第二種情況來說如下圖
此時A,B,C,D的所佔內存大小爲1,8,8,12
有的編譯器會顯示1,8,8,16.
出現這兩種情況原因是關於Allgnment的限制,和不同編譯器對Allgnment的解決方式不同所導致不同的優化結果。
1,8,8,12.這種情況是現在主流編輯器的情況,爲什麼這麼說呢?
因爲對於C++OO來說:
“一個empty virtualbase class,被視爲derived interface class object的一部分。”,也就是說我們並不會用empty virtual base class定義任何對象。所以就可以無視c++標準的規定。所以在derived 的情況下,並不會佔用空間。
接下來說說關於數據成員的綁定(thebinding of data member)
關於數據成員綁定有些東西沒必要談,因爲有些成員綁定的演進是由於程序員不同的寫法而造成了編譯器的對這個行爲的“妥協”。只要說一句“對於一個類成員函數的分析會被延遲到"}"的出現纔開始分析的”。
關於其他說一下typedef
typedef int length;
class a {
public:
length member;
private:
typedef long length;
};
在這個class 中,我們可能會有一些因直覺而失去正確的判斷,認爲class member length的類型爲 long,其實是global typedef。
因此在typedef的定義中應該避免nested(嵌套) typedef。
:using的情況也是和typedef一樣。
我們再來看看data member的佈局(datamember layout)
//c++class類對象單獨享有類成員(no-static),而共享類成員函數。
對於data member的佈局我們看看c++11怎麼說:
摘自c++11 standrad
No-staticdata members of class with the same access control are allocated so that latermembers has highter addresses with a class object
access(指的是private,public,protected等區段)
而對於相同區段的重複定義則,編譯器可以按照自己的方式去定義不同區段的排列順序,實際上大家沒有一個反人類的心去改變順序,而是選擇直接按照區段順序定義data member。
這裏我們不說多態,與多繼承,虛擬繼承的情況。
談一下類成員的存取,對於no-static members的存取就一定要談到面向對象的精髓-多態,說到多態貌似不談設計模式就感覺少了點什麼,好吧,接着類成員的存取接着說,指針調用類成員函數與類成員和直接調用成員
函數與類成員產生的差異與否都是和多態與virtual derive 有關。
關於c++ virtual我可能會花時間,寫一篇博文來鞏固自己的認知,這裏就不再贅述。
對於static members來說,取staticmembers 的地址就是取得它位於內存中的地址並不是偏移量,它可以看做是global的。但是被class限制,此時的類具有等效於一個namespace。
關於單一繼承與多繼承:
對於與單一繼承,c++保證在derived class中保證 base class object 對象的完整性,這就是說,因爲Allgnment而導致的字節擴充也會被繼承下來。(空類是其中特殊的一環)
而單一繼承的問題是對於派生類的主要問題發生在derived class object向下轉換的時候可能需要編譯器的介入。
一個多重類的派生對象在將地址指向最左端的base class 時候,情況與單一繼承相同(因爲保證了對象的完整性),而賦予第二個對象的時候就需要調整指針位置。
看個例子:
class A {....};
class B :A {...};
class C {...};
class D :B, C {...};
對於
D想B的轉化我們直接將D的地址交給B即可。
而對於向C的轉化我們需要做如下操作。
D* d=new …;
C* c;
c =d;->-> c=(C)((char*)d + sizeof(b));
//(這個轉化對於現在來說並不是很標準,c++11提供的轉化更加專業)
當d爲null pointer的時候,就會遇到麻煩,我們稍稍改進一下:
c=d
?(C)((char*)d + sizeof(b))
: 0;
關於虛擬繼承,將會在下一篇文章和data members pointer中一併寫出。
參考資料:深度探索c++對象模型(侯捷譯著),BOOST程序庫探祕。 c++11 standrad
與之相關的資料:Bjarne Stroustrup's homepage:http://www.stroustrup.com/index.html