1. 程式人生 > >第1章關於對象

第1章關於對象

type png stream 存在 lock 引用 多少 1.3 意思

第1章關於對象

數據成員直接放在每一個類對象之中. 而成員函數雖在class的聲明之內, 卻不出現在對象之中. 每一個非內聯成員函數值會誕生一個函數實例. 至於每一個"擁有零個或一個定義"的內聯函數則會在其每一個使用者(模塊)身上產生一個函數實例

  • virtual function機制, 用以支持一個有效的"執行期綁定"
  • virtual base class用以實現"多次出現在繼承體系中的base class, 有一個單一而被共享的實例"

1.1 C++對象模型

C++中, 有兩種數據成員: static和nonstatic, 以及三種類成員函數: static, nonstatic和virtual.

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;
};

簡單對象模型: 成員本身並不放在對象之中. 只有"指向成員的指針"才放在object內. 這麽做可以避免"members有不同的類型, 因而需要不同的存儲空間"所招致的問題. 它可能是為了盡量減低C++編譯器的設計復雜度而開發出來的, 賠上的則是空間和執行期的效率.

技術分享圖片

表格驅動對象模型: 為對所有類的對象都有一致的表達方式, 把所有與成員相關的信息抽取出來, 放在一個data member table和一個member functio table指針, 類對象本身則內含指向這兩個表格的指針.
技術分享圖片

C++ 對象模型: 非靜態數據成員被配置於每一個類對象之內, 靜態數據成員則被存放在個別的類對象之外. 靜態和非靜態函數成員也被存放在個別的類對象之外, 虛函數則以兩個步驟支持之:

  1. 每一個類產生出一堆指向虛函數的指針, 放在表格指針, 這個表格被稱為虛函數表(virtual table, vtbal)
  2. 每一個類對象被安插一個指針, 指向相關的虛函數表. 通常這個指針被稱為vptr. vptr的設定和重置都有每一個類的constructor, destructor和copy assignment運算符自動完成. 每一個類所關聯的type_info object(用以支持runtime type identification, RNTI)也經由虛函數表指出, 通常放在表格的第一個slot
    技術分享圖片

加上繼承: C++支持單一繼承, 多繼承和虛繼承(在虛擬繼承的情況下, 基類不管在繼承串鏈中被派生多少次, 永遠只會存在一個實例, 例如iostream之中就只有virtual ios基類的一個實例)

派生類模塑其基類實例:

  1. "簡單對象模型": 每一個基類可以被派生類實內的一個slot指出, 該slot含有基類對象的地址. 主要缺點是, 因為間接性而導致的空間和存取時間上的額外負擔, 優點則是類對象的大小不會因其基類的改變而改變
  2. base class table: 這裏所說的base class table被產生出來時, 表格中的每一個slot內含一個相關的基類地址, 這很像虛函數表內涵每一個虛函數的地址一樣. 每一個類對象內含有一個bptr, 他會被初始化, 指向其base class table. 主要缺點是由於間接性而導致空間和存取時間上的額外負擔, 優點則是在每一個類對象中對於繼承都有一致的表現方式: 每一個類對象都應該在某個固定位置上安防一個base table(基類表?)指針, 與基類的大小或個數無關. 第二個有點事, 無須改變類對象本身, 就可以放大, 縮小, 或更改基類表
    技術分享圖片

對象模型如何影響程序
不同的對象模型, 會導致"現有的程序代碼必須修改"以及"必須加入新的程序代碼"連個結果

X foobar() {
    X xx;
    X *px = new X;
    
    // foo()是一個virtual function
    xx.foo();
    px->foo();

    delete px;
    return xx;
};

這個函數有可能在內部被轉化為

X foobar() {
    X xx;
    X *px = new X;
    
    // foo()是一個virtual function
    xx.foo();
    px->foo();

    delete px;
    return xx;
};

技術分享圖片

1.2 關鍵詞所帶來的差異

關鍵詞困惑
兩個node重定義

class node {};

struct node {};

報錯

template<struct Type>
struct number {};

不報錯

template<class Type>
struct numble {};

策略性正確的struct
待補充

1.3 對象的差異

C++程序設計模型直接支持三種程序設計規範(programming paradigms): 1. 程序模型; 2. 抽象數據類型模型; 3. 面向對象模型

雖然可以直接或簡介處理繼承體系中的一個基類對象, 但只有通過指針引用的間接處理, 才支持OO程序設計所需的多態性質

thing1的定義和運用逸出了OO的習慣, 它反應的是ADT paradigm的行為

Library_materials thing1;

// class Book : public Library_materials { ... };
Book book;

// thing1不是一個Book!
// book被裁減
// 不過thing1仍保有一個Library_materials
thing1 = book;

// 調用的是Library_maternal::check_in()
thing1.check_in();

thing2的定義和語言, 是OO paradigm中的一個良好例證

// OK: 現在thing2參考到book
Library_maternal &thing2 = book;

// OK 現在引發的是Book::check_in()
thing2.check_in();

在OO paradigm之中, 程序員需要處理一個位置實例, 它的類型雖然有所界定, 卻有無窮可能. 這組類型受限於其繼承體系, 然而該體系理論上沒有深度和廣度的限制. 在C++中, 只有通過指針和引用的操作來完成. 相反, 在ADT paradigm中, 程序員處理的是一個擁有固定而單一類型的實例, 它在編譯期間就已經完全定義好了

// 描述objects: 不確定類型
Librar_maternal *px = retrieve_some_maternal();
Librar_maternal &rx = *px;

// 描述已知物: 不可能有令人驚訝的結果產生
Librar_maternal dx = *px

沒有辦法確定地說出px或tx到底指向何種類型對象, 只能確定要麽是一個Librar_maternal類型要麽就是Librar_maternal的子類型. dx可以確定是Librar_maternal類的一個對象

C++中指針或引用的處理不是多態的必要結果

// 沒有多態, 因為操作對象不是class object
int *pi;

// 沒有語言所支持的多態, 因為操作對象不是class object
void *pvi;

// ok: class x視為一個基類, 可以有多態效果
x *px;

一般而言類對象所需內存要有

  • 其非靜態數據成員的總和大小
  • 加上由於alignment的需求而填補上去的空間(可能存在於成員之間, 也可能存在於集合體邊界), 好像就是字節對齊的意思
  • 加上為了支持virtual而由內部產生的任何額外的負擔

指針的類型

指針的大小都是一樣的, 都是存儲指向對象的地址, 區別僅僅是"指針類型", 他會知道編譯器如何解釋某個特定地址中的內存內容及大小, 即影響解釋方式

class ZooAnimal {
public:
    ZooAnimal();
    virtual ~ZooAnimal();
    // ...
    virtual void  rotate();
protected:
    int loc; 
    string name;
};

ZooAnimal za("Zoey");
ZooAnimal *pza = &za;

技術分享圖片

class Bear : public ZooAnimal {
public:
    Bear();
    ~Bear();
    // ...
    void rotate();
    virtual void dance();
    // ...
protected:
    enum Dances {...};
    Dance dances_know;
    int cell_block;
};

Bear b("Yogi");
Bear *pb = &b;
Bear &rb = *pb;

技術分享圖片

她們每個都指向Bear對象的第一個byte. 期間的差別是, pb所涵蓋的地址包含整個Bear對象, 而pz所涵蓋的地址只包含Bear對象中的ZooAnimal, 除了ZooAnimal中出現的成員, 不能使用pz來直接處理Bear的任何成員, 可通過轉換pz的類型間接實現. 唯一例外的是通過virtual機制

Bear b;
ZooAnimal za = b;   // 會引起切割
// 調用ZooAnimal::rotate()
za.rotate();

這裏會引起兩個問題: 1.為什麽rotate()所調用的是ZooAnimal實例而不是Bear實例? 2.如果初始化函數講一個對象內容完整拷貝到另一個對象去, 為什麽za的vptr不指向Bear的virtual table

  1. za並不是(而且也絕不會是)一個Bear, 它是(並且只能是)一個ZooAnimal. 多態所造成的"一個以上的類型"的潛在力量, 並不能夠實際發揮在"直接存儲對象"這件事情上. 有一個似是而非的觀念: OO程序設計並不支持對object的直接處理.
  2. 編譯器在(1)初始化及(2)指定操作(將一個類對象指定給另一個類對象)之間做出了仲裁. 編譯器必須確保如果某個object含有一個或一個以上的vptrs, 那些vptrs的內容不會被基類對象初始化或改變

第1章關於對象