《深度探索C++對象模型》讀書筆記
前 言 Stanley B.Lippman
1. 任何對象模型都需要的三種轉換風味:
ü 與編譯器息息相關的轉換
ü 語言語義轉換
ü 程序代碼和對象模型的轉換
2. C++對象模型的兩種解釋
ü 語言中直接支持面向對象程序設計的部分
ü 對于各種支持的底層實現機制
3. C++ class的完整virtual functions在編譯時期就固定下來了,程序員沒有辦法在執行期動態增加或取代其中某一個。這使得虛擬函數調用操作得以有快速的派送結果,付出的卻是執行期的彈性。
4. 目前所有編譯器對于virtual function的實現都是使用各個class專屬的virtual table,大小固定,并且在程序執行前就構造好了。
5. C++對象模型的底層機制并未標準化,它會因實現品(編譯器)和時間的變動而不同。
2002-6-23
關于對象 Object Lessons
1.1 C++對象模式
1. C++在布局以及存取時間上主要的額外負擔是由virtual引起的,包括virtual function機制和virtual base class 機制,還有一些發生在“一個derived class和其第二或后繼之base class的轉換”上的多重繼承。
2. 在C++對象模型中,nonstatic data members被配置于每一個class object之內,static data members則被存放在所有的class object之外,static和nonstatic function members也被放在所有的class object之外,virtual functions則以兩個步驟支持:每個class產生一堆指向virtual functions的指針,放在virtual table (vtbl)中;每個class object被添加一個指針vptr,指向相關的virtual table。每個class所關聯的type_info object也經由vtbl指出,通常是放在vtbl的第一個slot處。vptr由每一個class的construtor、destructor以及copy assignment operator自動完成。以上模型的主要優點在于空間和存取時間的效率,主要缺點是,只要應用程序所用到的class object的nonstatic data members有所修改,那么應用程序代碼就必須重新編譯。
3. C++最初所采用的繼承模型并不運用任何間接性,base class subobject的data members直接放置于derived class object中。優點是提供對base class members緊湊且高效的存取,缺點是base class members的任何改變,都將導致使用其derived class 的object的應用程序代碼必須重新編譯。
4. virtual base class的原始模型是在class object中為每一個有關聯的virtual base class加上一個指針,其他演化出來的模型不是導入一個virtual base class table,就是擴充原已存在的vtbl,用以維護每一個virtual base class的位置。
1.2關鍵詞所帶來的差異
1. 可以說關鍵詞struct的使用伴隨著一個public接口的聲明,也可以說它的用途只是為了方便C程序員遷徙至C++部落。
2. C++中凡處于同一個access section的數據,必定保證以聲明次序出現在內存布局中,然而被放在多個access sections中的各筆數據排列次序就不一定了。同樣,base classes和derived classes的data members的布局也沒有誰先誰后的強制規定。
3. 組合composition而非繼承才是把C和C++結合在一起的唯一可行方法。
1.3對象的差異
1. C++程序設計模型支持三種程序設計典范programming paradigms:
ü 程序模型procedural model
ü 抽象數據類型模型abstract data type model, ADT
ü 面向對象數據模型object-oriented model,OO
2. 雖然可以直接或間接處理繼承體系中的一個base class object,但只有通過pointer或reference的間接處理,才能支持OO程序設計所需的多態性質。
3. C++中,多態只存在于public class體系中,nonpublic的派生行為以及類型為void*的指針可以說是多態,但它們沒有被語言明白地支持,必須由程序員通過顯示的轉型操作來管理。
4. C++以下列方法支持多態:
ü 經由一組隱含的轉化操作,如把一個derived class指針轉化為一個指向其public base type的指針;
ü 經由虛擬機制;
ü 經由dynamic_cast和typeid運算符。
5. 多態的主要用途是經由一個共同的接口來影響類型的封裝,這個接口通常被定義在一個抽象的base class中。這個接口是以virtual function機制引發的,它可以在執行期根據object的真正類型解析出到底是哪一個函數實體被調用。
6. 一個class object所需的內存,一般而言由以下部分組成:
ü nonstatic data members的總和大小;
ü 任何由于alignment需求而填補上去的空間;
ü 為支持virtual而由內部產生的任何額外負擔。
7. 一個pointer或reference,不管它指向哪一種數據類型,指針本身所需的內存大小是固定的。本質上,一個reference通常是以一個指針來實現,而object語法如果轉換為間接手法,就需要一個指針。
8. 指向不同類型之指針的差異,既不在其指針表示法不同,也不在其內容不同,而是在其所尋址出來的object類型不同,亦即指針類型會教導編譯器如何解釋某個特定地址中的內存內容及大小。它們之所以支持多態,是因為它們并不引發內存中任何與類型有關的內存委托操作,會受到改變的只是它們所指向的內存的大小和內容的解釋方式。
9. 轉型cast操作其實是一種編譯指令,大部分情況下它并不改變一個指針所含的真正地址,它只是影響被指向之內存的大小和內容的解釋方式。
10.一個base class object被直接初始化或指定為一個derived object時,derived object就會被切割sliced,以塞入較小的base type內存中,多態于是不再呈現。一個嚴格的編譯器可以在編譯時期解析一個通過該object而觸發的virtual function調用操作,從而回避virtual機制。這時,如果virtual function被定義為inline,則會有效率上的收獲。
11.C++通過class的pointer和reference來支持多態,這種程序設計風格就是所謂的OO;C++也支持具體的ADT程序風格,如今被稱為object-based OB,不支持多態,不支持類型的擴充。
2002-6-25
構造函數語意學The Semantics of Constructors
1. Jerry Schwarz,iostream函數庫建構師,曾為了讓cin能夠求得一個真假值,于是他為它定義了一個conversion運算符operator int()。但在語句cin << intVal中,其行為出乎意料:程序原本要的是cout而不是cin!但是編譯器卻找到一個正確的詮釋:將cin轉型為整型,現在left shift operator <<就可以工作了!這就是所謂的“Schwarz Error”。Jerry最后以operator void *()取代operator int()。
2. 引入關鍵詞explicit的目的,就是為了提供程序員一種方法,使他們能夠制止單一參數的constructor被當作一個conversion運算符。其引入是明智的,但其測試應該是殘酷的!
2.1 Default Constructor的建構操作
1. global objects的內存保證會在程序激活的時候被清為0。local objects配置于程序的堆棧中,heap objects配置于自由空間中,都不一定會被清為0,它們的內容將是內存上次被使用后的遺跡。
2. 在各個不同的版本模塊中,編譯器避免合成出多個default constructor的方法:把合成的default constructor、copy constructor、assignment copy operator都以inline方式完成。一個inline函數有靜態鏈接,不會被檔案以外者看到。如果函數過于復雜,不適合做成inline,就會合成一個explicit non-inline static實體。
3. 以下四種情況,編譯器必須為未聲明constructor的classes合成一個implicit nontrivial default constructor:帶有default constructor的member class object,帶有default constructor的base class,帶有virtual function,帶有virtual base class。其它各種情況且沒有聲明任何constructor的classes,它們擁有的是implicit trival default constructors,它們實際上并不會被合成出來。
4. 編譯器合成implicit nontrivial default constructor,不過是暗地里作了一些重要的事情以保證程序正確合理地運行。如果程序員提供了多個constructors,但其中都沒有default constructor,編譯器同樣會在這些constructors中插入一些相同功能的代碼,這些代碼都將被安插在explicit user code之前。
2002-6-26
2.2 Copy Constructor的建構操作
1. 有三種情況,會以一個class的內容作為另一個class object的初值:
ü 對一個object作明確的初始化操作,如:someClass obt = obtb;
ü 一個object被當作參數交給某個函數時,如:foo(obt);
ü 當函數返回一個class object時。
若class設計者明確定義了一個copy constructor,大部分情況下,該constructor會被調用。這可能導致一個暫時性的class object的產生或程序代碼的蛻變,或者兩者皆有。
2. 如果class沒有提供一個explicit copy constructor,當class object以相同class的另一個object作為初值時,其內部是以所謂的default memberwise initialization手法完成的,即把每一個內建的或派生的data member的值,從一個object拷貝到另一個object。不過,它并不會拷貝其中的member class object,而是以遞歸的方式施行memberwise initialization。
3. 一個class object可以從兩種方式復制得到:初始化和指定,從概念上而言,這兩個操作分別是以copy constructor和copy assignment operator完成的。
4. 如果class沒有聲明一個copy constructor,就會有隱含的聲明implicitly declared或隱含的定義implicitly defined出現。C++把copy constructor分為trivial和nontrivial兩種。只有nontrivial的實體才會被合成出來。決定一個copy constructor是否為trivial的標準在于class是否展現出所謂的“bitwise copy semantics”。
5. 以下四種情況,一個class不展現bitwise copy semantics:
ü class內含一個member object而后者的class聲明有或被編譯器合成有一個copy constructor時;
ü class繼承自一個base class而后者存在或被編譯器合成有一個copy constructor時;
ü 當class聲明了一個或多個virtual functions時;
ü 當class派生自一個繼承串鏈,其中有一個或多個virtual base classes時。
前兩種情況中,編譯器必須將member或base class的copy constructors調用操作安插到被合成的copy constructor中。
6. 一旦一個class object中必須引入vptr,編譯器就必須為它的vptr正確地設置好初值。此時,該class就不再展現bitwise semantics。
7. 當一個base class object以其derived class object內容作初始化操作時,其vptr復制操作必須保證安全。
8. 每一個編譯器對于虛擬繼承的承諾,都表示必須讓derived class object中的virtual base class subobject的位置在執行期準備妥當。維護位置的完整性是編譯器的責任。
2002-6-27
2.3 程序轉化語意學
1. 每一個明確的初始化操作都會有兩個必要的程序轉化階段:先重寫每一個定義,剝除其中的初始化操作,然后安插class的copy constructor調用操作。
2. 把一個class object當作參數傳給一個函數或是作為一個函數的返回值,相當于以下形式的初始化操作:
X xx = arg; 其中xx代表形式參數或返回值,而arg代表真正的參數值。
3. 函數定義如下:X bar(){X xx; return xx;},bar()的返回值通過一個雙階轉化從局部對象xx中拷貝出來:
ü 首先為bar添加一個額外參數,類型是class object的一個reference,這個參數用來放置被拷貝構建而得的返回值。
ü 然后在return指令之前安插一個copy constructor調用操作,以便將欲傳回之object的內容當作上述新增參數的初值,同時重寫函數使它不返回任何值。
4. Named Return Value(NRV)優化如今被視為是標準C++編譯器的一個義不容辭的優化操作,它的特點是直接操作新添加的額外參數。注意只有copy constructor的出現才會激活C++編譯器的NRV優化!NRV優化雖然極大地改善了效率,但還是飽受批評:一是優化由編譯器默默完成,而是否完成以及其完成程度完全透明;二是一旦函數變得比較復雜,優化就變得較難施行;三是優化由可能使程序產生錯誤——有時并不是對稱地調用constructor和destructor,而是copy constructor未被調用!
5. 在編譯器提供NRV優化的前提下,如果可以預見class需要大量的memberwise初始化操作,比如以by value的方式傳回objects,那么提供一個explicit inline copy constructor的函數實體就非常合理。此種情況下,沒有必要同時提供explicit assignment operator定義。
6. copy constructor的應用迫使編譯器多多少少對程序代碼作部分優化,尤其是當一個函數以by value的方式傳回一個class object,而該class有一個copy constructor(或定義或合成)時,無論在函數的定義還是在使用上將導致深奧的程序轉化。此外,編譯器將實施NRV優化。
7. 注意正確使用memset()和memcpy(),它們都只有在classes不含任何由編譯器產生的內部members如vptr時才能有效運行!
2002-6-30
2.4 成員初始化列表
1. 當寫下一個constructor時,就有機會設定class members的初值。不是經由member initialization list,就是在constructor函數本身之內。
2. 下列情況,為了讓程序能被順利編譯,必須使用member initialization list:
ü 初始化一個reference member時;
ü 初始化一個const member時;
ü 調用一個base class的constructor,而它擁有一組參數時;
ü 調用一個member class的constructor,而它擁有一組參數時。
3. 編譯器會對initialization list一一處理并可能重新排序,以反映出members的聲明次序,它會安插一些代碼到constructor內,并置于任何explicit user code之前。
4. 一個忠告:請使用“存在于constructor體內的一個member”,而不是“存在于member initialization list中的一個member”,來為另一個member設定初值。
2002-7-1
Data語意學 The Semantics of Data
討論如下繼承體系:
class X{};
class Y : public virtual X{};
class Z : public virtual X{};
class A: public Y, public Z{};
1. 一個empty class如class X{},它有一個隱晦的1 byte,那是被編譯器安插進去的一個char,使得這個class的兩個objects得以在內存中配置獨一無二的地址。
2. Y和Z的大小受到三個因素的影響:
ü 語言本身所造成的額外負擔overhead。語言支持virtual base classes時導致的額外負擔反映在某種形式的指針身上,它要么指向virtual base class subobject,要么指向一個存放virtual base class subobject地址或者其偏移量offset的表格。
ü 編譯器對于特殊情況所提供的優化處理。virtual base class X 1 byte大小的subobject也出現在class Y和Z身上。傳統上它被放在derived class的固定部分的尾端。某些編譯器對empty virtual base提供特殊處理,將它視為derived class object最開頭的一部分,它不用會任何的額外空間,也就是前面提到的1 byte。
ü Alignment的限制。Alignment就是將數值調整到某數的整數倍,在32位計算機上,通常該數為4 bytes(32位),以使bus的運輸量達到最高效率。
3. 一個virtual base class subobject只會在derived class中存在一份實體,不管它在class繼承體系中出現了多少次,class A的大小由下列幾點決定:
ü 被大家共享的唯一一個class X實體,大小為1 byte;
ü Base Y、Z的大小減去因virual base class而配置的大小;
ü class A自己的大小;
ü class A的alignment數量。
4. C++ standard并不強制規定base class subobjects、不同存取級別的data members的排列次序這種瑣碎細節,它也不規定virtual function以及virtual base classes的實現細節。
5. C++對象模型盡量以空間優化和存取速度優化來表現nonstatic data members,并且保持和C語言struct數據配置的兼容性。它把數據直接存放在每一個class object中,對于繼承而來的nonstatic data members,不管是virtual或nonvirtual base class也是如此。至于static data members則被放置在程序的一個global data segment中,不會影響個別class object的大小。static data member永遠只存在一份實體,但是一個template class的static data member的行為稍有不同。
3.1 Data Member的綁定
inline member function軀體內的data member綁定操作,會在整個class聲明完成后才發生,而argument list中的名稱還是會在它們第一次遭遇時被適當地決議resolved完成。基于這種狀況,請始終把nested type聲明放在class的起始處。
2002-7-2
3.2 Data Member的布局
1. 每一個private、protected、public區段就是一個access section。C++ Standard要求,在同一個access section中,members的排列只需滿足“較晚出現的members在class object中有較高的地址”這一條件即可。也就是說各個members并不一定的連續排列,alignment可能需要的bytes以及編譯器可能合成供內部使用的data members都可能介于被聲明的members之間。
2. C++ Standard也允許編譯器將多個access sections之中的data members自由排列,不必在乎它們出現在class聲明中的次序。當前各家編譯器都是把一個以上的access sections連鎖在一起,依照聲明的次序成為一個連續區塊。access sections的多寡不會導致額外負擔。
3. vptr傳統上會被放在所有明確聲明的members的最后,不過如今也有一些編譯器把vptr放在class object的最前端。
4. 一個用來判斷哪一個section先出現的template function:
template <class class_type, class data_type1, class data_type2>
char* access_order(data_type1 class_type::*mem1, data_type2 class_type::*mem2)
{
assert(mem1 != mem2);
return mem1 < mem2 ? “member 1 occurs first” : “member 2 occurs first”;
}
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/xjtuse_mal/archive/2007/03/01/1517806.aspx