響應(yīng)式做的好的網(wǎng)站有哪些怎么樣推廣自己的公司
Data 語義學(xué)(三)
“繼承” 與 Data member
上期的這個繼承的模塊我們還剩下一個虛擬繼承(virtual inheritance)沒有講,現(xiàn)在我們就來看看吧。
-
虛擬繼承(Virtual Inheritance)
虛擬繼承本質(zhì)就是:通過某種形式來實現(xiàn)共享繼承,使被繼承的類在繼承體系中只存在一個實例。最常用的就是:解決菱形繼承。
下面我們看一個熟悉的例子:
這樣我們就能夠很清楚的知道多重繼承和虛擬繼承的區(qū)別,而且我們也能看出在多重繼承的體系下,我們需要維護(hù)兩個 ios base class object ,這就造成了空間和效率上的浪費(fèi),我們不僅要為這兩個 ios object 分配空間,我們還要同步對他們的修改操作,來保證兩個 object 是一樣的。所以,解決這個問題的關(guān)鍵就是導(dǎo)入虛擬繼承(virtual inheritance)。
class ios { ... } class istream : public virtual ios { ... } class ostream : public virtual ios { ... } class iostream : public istream, public ostream { ... }
但是在編譯器中實現(xiàn)虛擬繼承難度很高:編譯器需要一個足夠有效的方法,將 istream 和 ostream 各自維護(hù)的一個 ios subobject,折疊成為一個由 iostream 維護(hù)的單一的 ios subobject,并且還可以保存 base class 和 derived class 的指針(以及 引用)之間的多態(tài)指定的操作。(polymorphism assignments)。
一般的實現(xiàn)方法是這樣的:**Class 如果內(nèi)含一個或多個 virtual base class subobjects,像 istream 那樣,將被分割成兩部分,一個不變區(qū)域和一個共享區(qū)域。**不變區(qū)中的數(shù)據(jù),不管后繼如何衍化,總是擁有固定的 offset,所以這一部分?jǐn)?shù)據(jù)可以被直接存取。至于共享區(qū)域,所表現(xiàn)的就是 virtual base class object。這一部分的數(shù)據(jù),其位置會因為每一個的派生操作而發(fā)生變化,所以它們只可以被間接存取。各家編譯器實現(xiàn)技術(shù)之間的差異就在于間接存取的方法不同。下面就為大家介紹這三種方法。
首先看看
Vertex3d
虛擬繼承的層次結(jié)構(gòu)。class Point2d { public:... protected:float _x, _y; };class Vertex : public virtual Point2d { public:.... protected:Vertex *next; };class Point3d : public virtual Point2d { public:... protected:float _z; };class Vertetx3d : public Vertex, public Point3d { public:... protected:float mumble; };// 繼承關(guān)系 // Point2d(_x, _y) // | // ____|____ // | | // Vertex(next) Point3d(_z) // | | // |________| // | // Vertex3d(mumble)
**一般的布局策略是先安排好 derived class 的不變部分,然后再建立其共享部分。**不同的編譯器對 virtual inheritance 的實現(xiàn)的不同就體現(xiàn)在共享部分的實現(xiàn)上。
-
第一個方法:在 derived class 種添加指向 virtual base class 的指針
直接上書上的例子
void Point3d::operator+=(const Point3d &rhs) {_x += rhs._x;_y += rhs._y;_z == rhs._z; }; // 在這種策略下,這個運(yùn)算符會被內(nèi)部轉(zhuǎn)換為 _vbcPoint2d->_x += rhs._vbcPoint2d->_x; _vbcPoint2d->_y += rhs._vbcPoint2d->_y; _z += rhs._z;// 現(xiàn)在我們考慮另一種情況 Point2d *p2d = pv3d; // 同樣在這種策略下,這個轉(zhuǎn)換也會被內(nèi)部轉(zhuǎn)換為 Point2d *p2d = pv3d ? pv3d->_vbcPoint2d : 0;
這個實現(xiàn)模型有兩個主要的缺點:
- 每一個對象必須針對其每一個 virtual base class 背負(fù)一個額外的指針。然而理想上我們卻希望 class object 有固定的負(fù)擔(dān),不因為其 virtual base class 的個數(shù)而變化。
- 由于虛擬繼承串鏈的加長,導(dǎo)致間接存取層次的增加。這里的意思是,如果我有三層虛擬派生,我就需要三次間接存取(經(jīng)由三個 virtual base class 指針)。然而理想上我們卻希望有固定的存取時間,不因為虛擬派生的深度而改變。
對于第二缺點,有些編譯器會選擇通過拷貝的操作取得所有的 nested virtual base class 指針,放到 derived class object 之中。這就解決了“固定存取時間”的問題,但是同時也付出一些空間上的代價。所以一般這些編譯會提供一個選項——詢問程序員是否要產(chǎn)生雙重指針。
看看模型的布局
對于第一個缺點,就引出了剩余的兩個解決方案。
-
Microsoft 編譯器引入了 virtual base class table。
每一個class object 如果有一個或多個 virtual class table,就會由編譯器安插一個指針,指向 virtual base class table。至于正真的 vitual base class pointer 將會被放在該表格中。
-
在 virtual function table 中放置 virtual base class 的 offset(而不是地址)。
以上面的繼承體系為例,我們看看在這種策略下,每一個類(class)的布局
上面的圖很直觀的呈現(xiàn)的這種將 virtual base class offset 和 virtual function table 結(jié)合的方法,virtual function table 可經(jīng)由正值或負(fù)值來索引。如果是正值,很顯然就是索引到了 virtual function table;如果是負(fù)值,則是索引到了 virtual base class offsets。
// 再來看看這個 operator void Point3d::operator+=(const Point3d &rhs) {_x += rhs._x;_y += rhs._y;_z += rhs._z; }// 在這種策略下,編譯器在內(nèi)部做的轉(zhuǎn)換如下 void Point3d::operator+=(const Point3d &rhs) {(this + _vptr_Point3d[-1])->_x += (&rhs + rhs._vptr_Point3d[-1])->_x;(this + _vptr_Point3d[-1])->_y += (&rhs + rhs._vptr_Point3d[-1])->_y;_z += rhs._z; }// 轉(zhuǎn)換操作 Point2d *p2d = pv3d; Point3d *p2d = pv3d ? pv3d + pv3d->_vptr_Point3d[-1] : 0;
上面的每一種方法都是一種實現(xiàn)模型,而不是一種標(biāo)準(zhǔn)。每一種模型都是用來解決 “存取 shared subobject 內(nèi)的數(shù)據(jù)(其位置會因每次派生操作而變化)”所引發(fā)的問題。
一般而言,virtual base class 最有效的運(yùn)用形式就是:一個抽象的 virtual base class,沒有任何 data member。
也就是我們所說的抽象類,在該類中定義純虛函數(shù)(pure virtual function),也稱為接口(interface)。
還有一小節(jié)講的是類成員指針(data member pointer),但是有點奇怪的是實驗的結(jié)果跟書上顯式的不一樣,這個等我弄明白了再更吧,如果你們知道為什么求求出個文章吧。
-