第1章關於對象
第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++ 對象模型: 非靜態數據成員被配置於每一個類對象之內, 靜態數據成員則被存放在個別的類對象之外. 靜態和非靜態函數成員也被存放在個別的類對象之外, 虛函數則以兩個步驟支持之:
- 每一個類產生出一堆指向虛函數的指針, 放在表格指針, 這個表格被稱為虛函數表(virtual table, vtbal)
- 每一個類對象被安插一個指針, 指向相關的虛函數表. 通常這個指針被稱為vptr. vptr的設定和重置都有每一個類的constructor, destructor和copy assignment運算符自動完成. 每一個類所關聯的type_info object(用以支持runtime type identification, RNTI)也經由虛函數表指出, 通常放在表格的第一個slot
加上繼承: C++支持單一繼承, 多繼承和虛繼承(在虛擬繼承的情況下, 基類不管在繼承串鏈中被派生多少次, 永遠只會存在一個實例, 例如iostream之中就只有virtual ios基類的一個實例)
派生類模塑其基類實例:
- "簡單對象模型": 每一個基類可以被派生類實內的一個slot指出, 該slot含有基類對象的地址. 主要缺點是, 因為間接性而導致的空間和存取時間上的額外負擔, 優點則是類對象的大小不會因其基類的改變而改變
- 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
- za並不是(而且也絕不會是)一個Bear, 它是(並且只能是)一個ZooAnimal. 多態所造成的"一個以上的類型"的潛在力量, 並不能夠實際發揮在"直接存儲對象"這件事情上. 有一個似是而非的觀念: OO程序設計並不支持對object的直接處理.
- 編譯器在(1)初始化及(2)指定操作(將一個類對象指定給另一個類對象)之間做出了仲裁. 編譯器必須確保如果某個object含有一個或一個以上的vptrs, 那些vptrs的內容不會被基類對象初始化或改變
第1章關於對象