2002-7-6
3.3 Data Member的存取
1. 不管什么情況,每一個static data member只有一個實體,放在程序的data segment之中,每次程序取用static member,不管是通過operator::還是member selection operator,都會被內部轉化為對該唯一extern實體的直接參考操作。每一個static member的存取以及與class的關聯不會導致任何執行時間或空間上的額外負擔。如果有兩個classes,每一個都聲明了一個static member freeList,那么當它們都放在程序的data segment時,就會導致名稱沖突,編譯器的解決方法是使用name-mangling,暗中對每一個static data member編碼,以獲得一個獨一無二的程序識別代碼。
2. 有多少個編譯器,就有多少種name-mangling做法,任何name-mangling做法都有兩個要點:
ü 一種算法,推導出獨一無二的名稱;
ü 如果編譯系統或者環境工具必須和使用者交談,那些獨一無二的名稱可被輕易推導回原先的名稱。
3. 取一個static data member的地址,會得到一個指向其數據類型的常量指針,而不是指向其class member的指針。
4. nonstatic data members直接放在每一個class object之中,除非經過顯示的explicit或隱含的implicit class object,沒有辦法直接存取它們。只要程序員在一個member function中直接處理一個nonstatic data member,所謂implicit class object就會發生,其實質是編譯器會為這個member function增添一個const this指針,而在函數體內通過這個this指針來存取nontatic data member。
5. 欲對一個nonstatic data member進行存取操作,編譯器需要把class object的起始地址加上data member的編譯量offset,如地址&someObject.someMember等于&someobject + (&theClass::someMember – 1);指向data member的指針,其offset值總是會被加上1,這樣可以使編譯系統區分出一個指向class第一個data member的指針和一個沒有指向任何data member的指針。
6. 每一個nonstatic data member的偏移量在編譯時期即可獲知,甚至如果member屬于一個單一或多重繼承體系中base class subobject也是一樣,因此其存取效率和一個C struct member或一個nonderived class的member的存取效率是一樣的。但是在虛擬繼承的情況下就另當別論了:如果該nonstatic data member是一個virtual base class的member,并且通過指針來存取的話,在編譯時期就不會得知這個member真正的offset位置,所以這個存取操作必須延遲至執行期,經由一個額外的間接導引才能夠解決。
2002-7-7
3.4 “繼承”與Data Member
1. 在C++繼承模型中,一個derived class object所表現出來的東西,是其自己的members加上其base classes members的總和。C++并未規定derived class members和base classes members的排列次序。不過,在大部分編譯器上,除virtual base class外,base class members總是先出現。
2. 一般而言,具體繼承concrete inheritance并不會增加空間或存取時間上的額外負擔。
3. 把兩個原本獨立不相干的classes湊成一對type/subtype,并帶有繼承關系容易犯兩個錯誤。一是可能會重復設計一些相同操作的函數,一般而言,選擇某些函數做成inline函數,是設計class的一個重要課題;二是把一個class分解為多層,有可能會為了表現class體系之抽象化,因為編譯器的邊界調整而膨脹所需空間。其根本原因是C++保證出現在derived class中的base class subobject有其完整原樣性。
4. C++最初問世時,許多編譯器把vptr放在class object的尾端,這樣可以保留base class C struct的對象布局。此后,某些編譯器開始把vptr放在class object的開始處,這樣會給多重繼承下通過指向class members之指針調用virtual function帶來一些幫助,否則,在執行期不僅必須備妥從class object起點處開始量起的offset,而且必須備妥class vptr之間的offset。
5. 單一繼承提供了一種自然多態的形態,是關于class體系中base type和derived type之間的轉換。一般來說,base class和derived class objects都是從相同的地址開始。但若將vptr放在class object的起始處,如果base class沒有virtual function而derived class有,那么單一繼承的自然多態就會打破。此時,把一個derived object轉換為其base類型就需要編譯器的介入,用以調整地址。而在既是多重繼承又是虛擬繼承的情況下,編譯器的介入則更有必要。
6. 多重繼承的復雜度在于derived class和其上一個base class乃至上上一個base class之間的非自然關系,其主要問題發生在derived class objects和其第二或后繼的base class objects之間的轉換。對一個多重派生對象,將其地址指定給最左端base class的指針,情況將和單一繼承相同,而第二個或后繼的base class的地址指定操作則需要修改地址,加上或減去(若是downcast)介于中間的base class subobjects的大小。C++并未要求多重繼承時derived class object中各個base class subjectes的排列次序,目前各個編譯器都是根據聲明次序來排列它們。
7. class內如果內含一個或多個virtual bass class subobjects,將被分割為兩部分:一個不變局部和一個共享局部。不變局部總是擁有固定的offset,其數據用以指定共享局部的位置,可以直接存??;而共享局部表現的就是virtual base class subobject,其位置會因為每次的派生操作而變化,只可間接存取。各家編譯器實現技術之間的差異就在于間接存取的方法不同。
8. 一般而言,virtual base class最有效的一種運用方式是:一個沒有任何data member的抽象class。
2002-7-14
3.5 對象成員的效率
如果沒有把優化開關打開,就很難猜測一個程序的效率表現,因為程序代碼潛在性的受到某些與編譯器有關的東西的影響。程序員如果關心效率,應該實際測試,不要光憑推論或常識判斷或假設。優化操作并不一定總是能夠有效運行。
2002-7-15
3.6 指向Data Members的指針
指向data members的指針可用來詳細調查class members的底層布局,可用來決定vptr是放在class的起始處還是尾端,還可用來決定class中access sections的次序。
取一個nonstatic data member的地址,將會得到它在class的offset;而取一個static data member的地址或者取一個綁定于真正class object身上的data member的地址,將會得到該member在內存中的真正地址。這也正是someType someClass::*和someTye *潛在的區別。
2002-7-16
Function語意學 The Semantics of Function
C++支持三種類型的member functions:static、nonstatic和virtual,每一種類型的調用方式都不同。
4.1 Members的各種調用方式
1. C++的設計準則之一便是nonstatic member function至少必須和一般的nonmember function有著相同的效率。編譯器內部會將member函數實體轉換為對等的nonmember函數實體,其步驟為:
ü 改寫函數原型signature以安插一個額外的參數this到member function中,使得class object可以調用該函數。其中,this是const指針,若該函數為const,則反映在this上面的結果是this指向的data也為const;
ü 將每一個對nonstatic data member的存取操作改為經由this指針來存取;
ü 將member function重新寫成一個外部函數,對函數名稱進行mangling處理;
此后,每一個函數調用操作也都必須轉換,用以提供相應的實參。
2. 關于虛擬函數的內部轉換步驟:若normalize是一個virtual member function,ptr->normalize();會被內部轉化為(*ptr->vptr[t])(ptr); 事實上,vptr名稱也會被mangled,因為可能存在有多個vptrs;t是vitrual table slot的索引值,關聯到normalize函數;第二個ptr表示this指針。
3. 使用class scope operator明確調用一個vitual function,或經由一個class object調用一個vitual function其決議方式會和nontatic member function一樣!故virtual function的一個inline函數實體可被擴展開來,因而提供極大的效率利益。
4. static member function的主要特征是沒有this指針,這導致它不能直接存取其class中的nonstatic members,不能被聲明為const、volatile或virtual,也不需要經由class object才能調用。static member function會被提出于class聲明之外,并給予一個經過mangled的適當名稱。如果取一個static member function的地址,得到的將是其在內存中的地址,其地址類型并不是一個指向class member function的指針,而是一個nonmember函數指針。static member function的一個意想不到的好處是可以成為一個callback函數,也可以成功地應用在thread函數身上。
2002-07-17
4.2 Virtual Member Functions虛擬成員函數
1. C++中,多態polymorphism表示以一個public base class指針或reference尋址出一個derived class object。識別一個class是否支持多態,唯一適當的方法試看它是否有任何virtual function。只要class擁有一個virtual function,它就需要一份額外的執行期型別判斷信息。
2. 一個class只會有一個virtual table,其中內含對應class object中所有的active virtual functions的函數實體的地址。這些active virtual functions包括:
ü 一個class定義的函數實體。它會改寫overriding一個可能存在的base class virtual function。
ü 繼承自base class的函數實體。此時該class不改寫base class virtual function。
ü 一個pure_virtual_called()函數實體,它既可以扮演pure virtual function的空間保衛者,也可以當作執行期異常處理函數。如果該函數被調用,通常的操作是結束程序。
3. 每一個virtual function都被指派一個固定不變的索引值,該值在整個繼承體系中保持與特定virtual function的關聯。這樣就可以在編譯時期設定virtual function的調用。
2002-7-20
4. 多重繼承下,一個上層basse classes數目為n的derived class,它將內含n-1個額外的virtual tables。其主要實體與最左端的base class共享,其中包含所有virtual functios的地址;n-1個次要實體與其它base classes有關,其中只包含出現在對應base class中virtual functions的地址。
5. 在多重繼承中支持virtual function,其復雜度圍繞在第二個及后繼base class上,以及執行期this指針調整上。第二(或后繼)base class會影響對virtual function支持的3種情況:
ü 通過指向第二個base class的指針,調用derived class virtual function;
ü 通過指向derived class的指針,調用第二個base class中一個繼承而來的virtual function;
ü 允許virtual function函數的返回值類型有所變化,可能是base type,也可能是publicly derived type。
6. 關于執行期this指針調整比較有效率的解決方法是thunk。所謂thunk是一小端assembly碼,用來以適當的offset值來調整this指針并跳到相應的virtual function。thunk技術允許virtual table slot繼續內含一個簡單的指針,此時多重繼承將不需要任何空間上的額外負擔!slots中的地址可以直接指向virtual function,也可以指向一個相關的thunk。
4.3 函數的效能
nonmember、static member和nonstatic member function在內部都會轉化為完全相同的形式,三者效率相同。
2002-08-08
4.4 指向Member Function的指針
對一個nonstatic member function取址,得到的是該函數在內存中的地址;而面對一個virtual function,得到的將是一個索引值。這個值是不完整的,必須被綁定于一個class object上,才能夠通過它調用函數。指向member function的指針的聲明語法,以及指向member selection運算符的指針,其作用是作為this指針的空間保留者。因此,static member function的類型是函數指針,而不是指向member function的指針。
使用一個member function指針,如果并不用于virtual function、多重繼承、virtual base class等情況的話,其成本并不比使用一個nonmember function指針要高。
4.5 Inline Functions
關鍵詞inline只是一項請求。如果在某個層次上,函數的執行成本比一般的函數調用及返回機制所帶來的負荷低,那么該請求被接受,編譯器就用一個表達式合理地將函數擴展開來。真正的inline函數擴展操作是在函數調用的那一點上。在inline擴展期間,每一個形式參數會被對應的實際參數所取代,inline函數中的每一個局部變量都必須被放在函數調用的一個封閉區段中,并擁有一個獨一無二的名稱。這會帶來參數的求值操作以及臨時性對象的管理。
2002-08-11
構造、解構、拷貝語意學 Semantics of Construction, Destruction, and Copy
1. 一般而言,class的data member應該被初始化,而且只在constructor中或其它member functions中初始化,其它任何操作都將破壞其封裝性質,使其維護和修改更加困難。
2. 可以定義并調用invoke一個pure virtual function,但它只能被靜態調用,不能經由虛擬機制調用。每一個derived class destructor會被編譯器加以擴展,靜態調用每一個virtual base class以及上一層base class的destructor。因此,不管base class的virtual destructor是否聲明為pure,它必須被定義。
5.1 無繼承情況下的對象構造
C++ Standard要求編譯器盡量延遲nontrivial members的實際合成操作,直到真正遇到其使用場所為止。
5.2 繼承體系下的對象構造
一般而言,繼承體系下編譯器對constructor所作的擴充操作以及次序大約如下:
ü 所有virtual base class constructors必須從左到右、從深到淺被調用:如果class被列于member initialization list中,那么任何明確指定的參數都必須傳遞過去,否則如果class有一個default constructor,也應該調用它;class中的每一個virtual base class subobject的偏移量offset必須在執行期可被存??;如果class object是最底層most-derived的class,其constructors可能被調用,某些用以支持這個行為的機制必須被方進來。
ü 以base class的聲明次序調用上一層base class constructors:如果base class被列于member initialization list中,那么任何明確指定的參數都必須傳遞過去,否則若它有default constructor或default memberwise copy constructor,那么就調用它;如果base class是多重繼承下的第二或后繼的base class,那么this指針必須有所調整。
ü 如果class object有virtual table pointer(s),它(們)必須被設定初值,指向適當的virtual table(s)。
ü 如果有一個member沒有出現在member initialization list中,但它有default constructor,調用之。
ü 將member initialization list中的data members的初始化操作以members的聲明次序放進constructor的函數本身。
2002-8-18
5.3對象復制語意學 Object Copy Semantics
1. 只有在默認行為所導致的語意不安全或者不正確以致發生別名化aliasing或者內存泄漏memory leak時,才需要設計一個copy assignment operator。否則,程序反倒會執行得較慢。
2. 如果僅僅是為了把NRV優化開關打開而提供一個copy constructor,那么就沒有必要一定要提供一個copy assignment operator。
3. copy assignment operator有一個非正交情況,那就是它缺乏一個平行于member initialization list的member assignment list。調用base class的copy assignment operator示例:
Point::operator = (p3d); 或 (*(Point*)this) = p3d; 或 (Point &)(*this) = p3d;
4. 事實上,copy assignment operator在虛擬繼承情況下行為不佳,需要小心設計和說明。許多編譯器甚至并不嘗試取得正確的語意,它們在每一個中間的copy assignment operator中調用每一個base class instance,于是造成virtual base copy assignment operator的多個實體被調用。建議盡可能不要允許一個virtual base class的拷貝操作,并不要在任何virtual base class中聲明data member。
5.5解構語意學 Semantics of Destruction
如果class沒有定義destructor,那么只有在其內帶的member object或base class擁有destructor時,編譯器才會自動合成出一個destructor。一個由程序員定義的destructor被擴展的方式類似constructors被擴展的方式,只是順序相反:
ü destructor的函數本體首先被執行;
ü 如果class擁有member class objects,而后者擁有destructors,那么它們將以聲明的相反順序而調用;
ü 如果object內帶一個vptr,則現在被重新設定以指向適當base class之virtual table;
ü 如果有任何直接的nonvirtual base classes擁有destructor,它們將以聲明的相反順序而調用;
ü 如果有任何virtual base classes擁有destructor,而前面討論的這個class是most-derived class,那么它們會以原先構造順序的相反順序被調用。
2002-8-19
執行期語意學 Runtime Semantics
6.1對象的構造和解構
1. 一般而言,constructor和destructor的安插都如你所預期。但如果一個區段或函數中有一個以上的離開點,情況就會復雜一些,destructor會放在每一個離開點之前。通常,我們要求將object盡可能放在使用它的那個程序區附近,這樣做可以節省不必要的對象產生和銷毀操作。
2. C++程序中所有的global objects都被放置在程序的data segment中,如果不明確指定初值,object所配置的內存內容將為0(C并不自動設定初值)。如果global object有constructor和destructor的話,我們說它需要靜態的初始化和內存釋放操作。
2002-8-20
3. virtual base class的subobject在每個derived class中的位置可能會變動,不能在編譯時期確定。以一個derived class的pointer或reference來存取virtual base class subobject,是一種nonconstant expression,必須在執行期方可評估求值。
4. 使用靜態初始化的object有一些缺點。其一,無法放入try區段,任何throw操作必將觸發exception handling library的默認函數terminate();其二,程序員必須為控制“需要跨越模塊做靜態初始化”objects的依賴順序而產生的復雜度付出代價。建議根本就不要使用那些需要靜態初始化的global objects。
5. 新的C++標準要求編譯單位中的static local class objects必須在相應函數第一次被調用時才被構造,而且必須以相反的次序銷毀。由于這些objects是在需要時才被構造,因此編譯時期無法預期其集合和順序。為支持新標準,可能要對被產生出來的static local class objects保持一個執行期鏈表。
2003-8-1
6. 對于對象數組定義,晚近的編譯器一般會提供兩個函數,分別用于處理沒有virtual base class的class,以及內帶virtual base class的class ,它們通常被稱為vec_new、vec_vnew。前者類型通常為:
void* vec_new( // 初始化程序員未提供初值的連續元素
void *array, // 數組起始地址若為0,則動態分配
size_t elem_size, // 每一個class object的大小
int elem_count, // 數組中的元素數目
void (*constructor) (void *), // class的default constructor指針
void (*destructor) (void *, char) // class的destructor指針,以0填入
); 如果程序員提供帶有默認參數值的default constructor,編譯器要做特殊處理,以傳入默認參數值!
對應銷毀數組的兩個函數分別為vec_delete、vec_vdelete。前者類型通常為:
void* vec_delete(
void *array, // 數組起始地址
size_t elem_size, // 每一個class object的大小
int elem_count, // 數組中的元素數目
void (*destructor) (void *, char) // class的destructor指針
);
6.2 new和delete運算符
注意區分operator new和new operator!前者負責分配內存;后者先調用前者分配內存,然后調用constructor以實施初始化。
完
《深度探索C++對象模型》讀書筆記
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/xjtuse_mal/archive/2007/03/01/1517809.aspx