在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;
};
在 Stroustrup當初設計的C++對象模型中,Nonstatic data members被配置於每一個class object之內, static data members則被存放在所有的class object之外。Static和nonstatic function members也被 放在所有的class object之外,Virtual functions則以兩個步驟支持之:
(1)每一個class產 生出一堆指向virtual functions的指針,放在表格之中,這個表格被稱為virtual table(vtbl)。
(2)每一個class object被添加了一個指針,指向相關的virtual table.通常這個指針被稱為 vptr.vptr的設定(setting)和重置(resetting)都由每一個class的constructor、destructor和copy assignment運算符自動完成。每一個class所關聯的type_info object(用以支持runtime type identification,RTTI)也經由virtual table被指出來,通常是放在表格的第一個slot處。
***虛 擬繼承***
繼承關系也可以指定為虛擬(virtual,也就是共享的意思):
class istream : virtual public ios ...{ ... };
class ostream : virtual public ios ...{ ... };
class iostream : public istream,public ostream ...{ ... };
在虛擬繼承 的情況下,base class不管在繼承串鏈中被派生(derived)多少次,永遠只會存在一個實體(稱為 subobject)。例如iostream之中就只有virtual ios base class的一個實體。
***指針的類型 ***
下面有一個ZooAnimal聲明:
class ZooAnimal ...{
public:
ZooAnimal();
virtual ~ZooAnimal();
// ...
virtual void rotate();
protected:
int loc;
String name;
};
一個指向類的指針與 一個指向整數的指針或一個指向template Array的指針之間的差異既不在其指針表示法不同,也不在於 其內容(代表一個地址)不同,而是在其所尋址出來的object類型不同。也就是說,“指針類型 ”會教導編譯器如何解釋某個特定地址中的內存內容及其大小:
1.一個指向地址1000的整 數指針,在32位機器上,將涵蓋地址空間1000~1003.
2.如果String是傳統的8-bytes(包括一個 4-bytes的字符指針和一個用來表示字符串長度的整數),再加上指向vtbl的指針vptr,那麼一個 ZooAnimal指針將橫跨地址空間1000~1015(4+8+4)。
假設Bear繼承於ZooAnimal,如下所示:
Bear b;
ZooAnimal *pz = &b;
Bear *pb = &b;
一個 Bear指針和一個ZooAnimal指針雖然都指向Bear Object的第一個byte,但pb所涵蓋的地址包含整個Bear Object,而pz所涵蓋的地址只包括Bear Object中的ZooAnimal subobject.
除了ZooAnimal subobject中出現的members,你不能夠使用pz來直接處理Bear的任何members.唯一例外是通過virtual機 制。
// 不合法:cell_block不是ZooAnimal的一個member
// 雖然我們知道pz當前 指向一個Bear object
pz->cell_block;
// ok: 經過一個明白的downcast操作就 沒有問題!
((Bear*)pz)->cell_block;
// 下面這樣更好,但它是一個run-time operation(成本較高)
if(Bear* pb2 = dynamic_cast<Bear*>(pz))
pb2- >cell_block;
// ok: 因為cell_block是Bear的一個member
pb- >cell_block;
***切割引起的編譯器仲裁***
Bear b;
ZooAnimal za = b; // 這會引起切割(sliced)
// 調用 ZooAnimal::rotate()
za.rotate ();
為什麼rotate所調用的是ZooAnimal實體而不是Bear實體?此外,如果初始化函數( 應用於上述assignment操作發生時)將一個object內容完整拷貝到另一個object中去,為什麼za的vptr 不指向Bear的virtual table?
第二個問題的答案是,編譯器在(1)初始化及(2)指定 (assignment)操作之間做出了仲裁。編譯器必須確保如果某個object含有一個或一個以上的vptrs,那 些vptrs的內容不會被base class object初始化或改變。
至於第一個問題的答案是:za並不是( 而且絕對不會是)一個Bear,它是(並且只能是)一個ZooAnimal.多態所造成的“一個以上的類型 ”的潛在力量,並不能夠實際發揮在“直接存取objects”這件事情上。
***面 對對象(OO)和基於對象(OB)***
基於對象的數據類型可以展示封裝的非多態形式,但是不支 持類型的擴充。一個OB設計可能比一個對等的OO設計速度更快而且空間更緊湊。速度快是因為所有的函 數引發操作都在編譯時期解析完成,對象建構起來時不需要設置virtual機制;空間緊湊則是因為每一個 class object不需要負擔傳統上為了支持virtual機制而需要的額外符合。不過,OB設計比較沒有彈性。