深入探索C++對象模型(一)


再讀《深入探索C++對象模型》筆記。

關於對象

C++在加入封裝后(只含有數據成員和普通成員函數)的布局成本增加了多少?

  • 答案是並沒有增加布局成本。就像C struct一樣,memeber functions雖然含在class的聲明之內,卻不出現在object中。每一個non-inline member function只會誕生一個函數實體。至於每一個“擁有零個或一個定義的” inline function則會在其每一個使用者(模塊)身上產生一個函數實體。

C++在布局以及存取時間上主要的額外負擔是由virtual引起的,包括:

  • virtual funciton機制,用以支持一個有效率的“執行期綁定”(runtime binding)
  • virtual base class,用以實現“多次出現在繼承體系中的base class,有一個單一而被共享的實體”

C++ 對象模式(The C++ Object Model)

在C++中,有兩種class data members:static 和 nonstatic,以及三種class member functions:static、nonstatic和virtual。已知下面這個class Point聲明:

class Point{
public:
    Point(float xval);
    virtual ~Point();
    float x() const;
    static int PointCount();

protected:
    virtual ostream& print(ostream &os) const;
    float _x;
    static int _point_count;
};

C++對象模型中,nonstatic data members被配置於每一個class object之內,static data members則被存放在所有的class object之外。static和nonstatic function members也被放在所有的class object之外。virtual function則以兩個步驟支持之:
1. 每個class產生出一堆指向virtual functions的指針,放在表格之中。這個表格被稱為virtual table(vtbl)
2. 每一個class object被安插一個指針,指向相關的virtual table。通常這個指針被稱為vptr。vptr的設定和重置都由每一個class的constructor、destructor和copy assignment運算符自動完成。每一個class所關聯的type_info object(用以支持runtime type identification, RTTI)也經由virtual table被指出來,通常放在表格的第一個slot處

故上面的聲明所對應的對象模型如下:

上圖說明了C++對象模型如何應用於Point Class身上,這個模型的主要優點在於它的空間和存取時間的效率。主要缺點是:如果應用程序代碼未曾改變,但所用到的class objects的nonstatic data members有所修改(有可能是增加、移除或更改),那么應用程序代碼同樣得重新編譯。

繼承關系可以指定為虛擬(virtual,也就是共享的意思):在虛擬繼承的情況下,base class不管在繼承鏈中被派生(derived)多少次,永遠只會存在一個實例(稱為subobject)。

class istream : virtual public ios{ ... };
class ostream : virtual public ios{ ... };

對象的差異

C++以下列方法支持多態:

  • 經由一組隱式的轉化操作。例如把一個derived class指針轉化為一個指向其public base type的指針
  • 經由virtual function機制
  • 經由dynamic_cast和typeid運算符

多態的主要用途是經由一個共同的接口來影響類型的封裝,這個接口通常被定義在一個抽象的base class中。這個共享接口是以virtual function機制引發的,它可以在執行期根據object的真正類型解析出到底是哪一個函數實體被調用。

需要多少內存才能表現一個class object?

  • 其nonstatic data members的總和大小
  • 加上任何由於aliginment的需求而填補上去的空間(可能存在於members之間,也可能存在於集合體邊界)
  • 加上為了支持virtual而由內部產生的任何額外負擔

一個指針(引用),不管它指向哪一種數據結構,指針本身所需的內存大小是固定的。

舉例如下:一個指向ZooAnimal的指針是如何地與一個指向整數得指針或一個指向template Array的指針有所不同的呢?

ZooAnimal *px;
int *pi;
Array<string> *pta;

以內存需求的觀點來說,沒有什么不同!它們三個都需要足夠的內存來放置一個機器地址(通常是個word)。“指向不同類型的各指針”間的差異,既不在其指針表示法不同,也不在其內容(代表一個地址)不同,而是在其所尋址出來的object類型不同,也就是說,“指針類型”會教導編譯器如何解釋某個特定地址中的內存內容及其大小。

轉型(cast)其實是一種編譯器指令。大部分情況下它並不改變一個指針所含的真正地址,它只影響“被指出之內存大大小和其內容”的解釋方式。

當一個base class object被直接初始化為(或被指定為)一個derived class object時,derived object就會被切割(sliced)以塞入較小的base type內存中,derived type將沒有留下任何蛛絲馬跡。多態於是不再呈現,而一個嚴格的編譯器可以在編譯器解析一個“通過此object而觸發的virtual function調用操作”,因而回避virtual機制。如果virtual function被定義為inline,則更有效率上的大收獲。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM