1.C++在佈局以及存取時間上主要的額外負擔是由virtual引起,包括:
1>virtual function機制:用以支持一個有效率的“執行期綁定”(runtime binding).
2>virtual base class: 用以實現“多次出現在繼承體系中的“base class, 有一個單一而被共享的實體”。
2.在C++中,有兩種class data members: static和nonstatic,以及三種class member functions: static、nonstatic和virtual。
3.C++對象模型。在此模型中,Nonstatic data members被置於每一個class object之內,static data members則被存放在所有的class object之外。Static和non static 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 identificatio,RTTI)也經由virtual table被指出來,通常是放在表格的第一個slot處。
class Point
{
public:
Point(flot xval);
virtual ~Point();
float x() const;
static int PointCount();
protected:
virtual ostream& print(ostream& os) const;
float _x;
static int _point_count;
};
4.虛擬繼承。
class iostream:
public istream,
public ostream
{...};
class istream: virtual public ios {...};
class ostream: virtual public ios {...};
在虛擬繼承的情況下,base class不管在繼承串鏈中被派生(derived)多少次,永遠只會存在一個實體(稱爲subobject)。例如iostream之中就只有virtual ios base class的一個實體。
5.C++中凡處於同一個access section的數據,必定保證以其聲明次序出現在內存佈局當中。然後被放置在多個access sections中的各筆數據,排列次序就不一定了。
6.C++以下列方法支持多態:
1>經由一組隱含的轉化操作。例如把一個derived class指針轉化爲一個指向public base type的指針:
shape *ps = new circle();
2>經由virtual function機制:
ps->rotate();
3>經由dynamic_cast和typeid運算符:
if ( circle* pc = dynamic_cast<circle*>(ps) )
...
7.需要多少內存才能夠表現一個class object? 一般而言要有:
1>其nonstatic data members的總和大小;
2>加上任何由於alignment的需求而填補(padding)上去的空間(可能存在於members之間,也可能存在於集合體邊界);
3>加上爲了支持virtual而由內部產生的任何額外負擔(overhead)。
8.指針的類型。一個指向ZooAnimal的指針是如何地與一個指向整數的指針或一個指向template Array(如下,與一個String一併產生)的指針有所不同呢?
ZooAnimal* px;
int* pi;
Array<String>* pta;
以內存需求的觀點來說,沒有什麼不同。它們三個都需要有足夠的內存來放置一個機器地址(通常是個word)。“指向不同類型之各指針”間的差異,既不在其指針表示法不同,也不在其內容(代表一個地址)不同,而是在其所尋址出來的object類型不同。也就是說,“指針類型”會教導編譯器如何解釋某個特定地址中的內存內容及其大小:
1>一個指向地址1000的整數指針,在32位機器上,將涵蓋地址空間1000~1003;
2>如果String是傳統的8-bytes(包括一個4-bytes的字符指針和一個用來表示字符串長度的整數),那麼一個ZooAnimal指針將橫跨地址空間1000~1015(4+8+4)
那麼,一個指向地址1000而類型爲void*的指針,將涵蓋怎樣的地址空間呢?是的,我們不知道!這就是爲什麼一個類型爲void*的指針只能夠含有一個地址,而不能夠通過它操作所指之object的緣故。
所以,轉型(cast)其實是一種編譯器指令。大部分情況下它並不改變一個指針所含的真正的地址,它隻影響“被指出之內存的大小和其內容”的解釋方式。
9.加上多態之後
class Bear: public ZooAnimal
{
public:
Bear();
~Bear();
// ...
void rotate();
virtual void dance();
// ...
protected:
enum Dances { ... };
Dances dances_known;
int cell_block;
};
Bear b("Yogi");
Bear* pb = &b;
Bear& rb = *pb;
假設Bear object放在地址1000處,一個Bear指針和一個ZooAnimal指針有什麼不同?
Bear b;
ZooAnimal* pz = &b;
Bear* pb = &b;
它們每個都指向Bear object的第一個byte,其間的差別是,pb所涵蓋的地址包含整個Bear object,而pz所涵蓋的地址只包含Bear object中的ZooAnimal subobject。除了ZooAnimal subject中出現過的members,你不能夠使用pz來直接處理Bear的任何members,唯一例外是通過virtual機制:
10.總而言之,多態是一種威力強大的設計機制,允許你繼一個抽象的public接口之後,封裝相關的類型。需要付出的代價就是額外的間接性——不論是在“內存的獲得”或是在“類型的決斷”上。C++通過class的pointers和references來支持多態,這種程序設計風格就成爲“面向對象”。