C 之虛函式

2022-11-27 12:33:04 字數 3603 閱讀 3050

《深度探索c++物件模型》是這樣來說多型的:

在c++中,多型表示「以乙個public base class的指標(或引用),定址出乙個derived class object」的意思。

消極多型與積極多型

用基類指標來定址繼承類的物件,我們可以這樣:

point ptr=new point3dpoint3d繼承自point

在這種情況下,多型可以在編譯期完成(虛基類情況除外),因此被稱作消極多型(沒有進行虛函式的呼叫)。相對於消極多型,則有積極多型——指向的物件型別需要在執行期在能決定1。積極多型的例子如虛函式和rtti:

//例1,虛函式的呼叫

ptr->z();

//例2,rtti 的應用

if(point3d *p=dynamic_cast(ptr) )

return p->z();

關於rtti的筆記可見筆記eh & rtti。本文主要精力將集中於虛函式上。對於乙個如上例關於虛函式的呼叫,要如何來保證在執行期呼叫的是正確的實體——point3d::

z()而不是呼叫了point::z()。來看看虛函式的實現機制吧,它將保證這一點。

單繼承下的虛函式

虛函式的實現:

為每個有虛函式的類配一張虛函式表,它儲存該類型別資訊和所有虛函式執行期的位址。

為每個有虛函式的類插入乙個指標(vptr),這個指標指向該類的虛函式表。

給每乙個虛函式指派乙個在表中的索引。

用這種模型來實現虛函式得益於在c++中,虛函式的位址在編譯期是可知的,而且這一位址是固定不變的。而且表的大小不會在執行期增大或減小。

乙個類的虛函式表中儲存有型別資訊(儲存在索引為0的位置)和所有虛函式位址,這些虛函式位址包括三種:

這個類定義的虛函式,會改寫(overriding)乙個可能存在的基類的虛函式實體——假如基類也定義有這個虛函式。

繼承自基類的虛函式實體,——基類定義有,而這個類卻沒有定義。直接繼承之。

乙個純虛函式實體。用來在虛函式表中佔座,有時候也可以當做執行期異常處理函式。

每乙個虛函式都被指派乙個固定的索引值,這個索引值在整個繼承體系中保持前後關聯,例如,假如在point虛函式表中的索引值為2,那麼在point3d虛函式表中的索引值也為2。

當乙個類單繼承自有虛函式的基類的時候,將按如下步驟構建虛函式表:

1. 繼承基類中宣告的虛函式——這些虛函式的實體地址被拷貝到繼承類中的虛函式表中對於的slot中。

2. 如果有改寫(override)基類的虛函式,那麼在1中應將改寫(override)的函式實體的位址放入對應的slot中而不是拷貝基類的。

3. 如果有定義新的虛函式,那麼將虛函式表擴大乙個slot以存放新的函式實體地址。

我們假設函式在point虛函式表中的索引為4,回到最初的問題——要如何來保證在執行期呼叫的是正確的z()實體?其中微妙在於,編譯將做乙個小小的轉換:

ptr->z();

//被編譯器轉化為:

(*ptr->vptr[4])(ptr);

這個轉換保證了呼叫到正確的實體,因為:

雖然我們不知道ptr所指的真正型別,但它可以通過vptr找到正確型別的虛函式表。

在整個繼承體系中z()的位址總是被放在slot 4。

多重繼承下的虛函式

在多重繼承下,繼承類需要為每一條繼承線路維護乙個虛函式表(也有可能這些表被合成為乙個,但本質意義並沒有變化)。當然這一切都發生在需要的情況下。

當使用第一繼承的基類指標來呼叫繼承類的虛函式的時候,與單繼承的情況沒有什麼異樣,問題出生在當以第二或後繼的基類指標(或引用)的使用上。例如:

//假設有這樣的繼承關係:class derived:public base1,public base2;

//base1,base2都定義有虛析構函式。

base2 *ptr = new derived;

//需要被轉換為,這個轉換在編譯期完成

base2 *ptr = temp ? temp + sizeof(base1) : 0 ;

如果不做出上面的轉換,那麼 ptr 指向的並不是 derived 的 base2 subobject 。後果是,ptr 將乙個derived型別當做base2型別來用。

當要時又面臨了一次轉換,因為在的時候,需要對整個物件而不是其子物件施行delete運算子,這期間需要調整ptr指向完整的物件起點,因為不論是呼叫正確的析構函式還是delete運算子都需要乙個指向物件起點的指標,想一想給予乙個derived類的成員函式指向base2 subobjuect 的this指標會發生什麼吧。因為ptr的具體型別並不知道,所以必須要等到執行期來完成。

bjame的解決方法是將每乙個虛函式表的slot 擴充套件,以使之存放乙個額外的偏移量。於是虛函式的呼叫:

(*ptr->vptr[1])(ptr);

//將變成:

(*ptr->vptr[1].addr)(ptr+*ptr->vptr[1].offset);

其中使用用以獲取正確的虛函式位址,而來獲得指向物件完整的起點。這種方法的缺點顯而易見,代價過大了一點,所有的情況都被這一種佔比較小的情況拖累。

還有一種叫做thunk的方法,thunk的作用在於:

1. 以適當的offset值來this調整指標.

2. 跳到虛函式中去。

thunk技術即是:虛函式表中的slot仍然繼續放乙個虛函式實體地址,但是如果呼叫這個虛函式需要進行this調整的話,該slot中的位址就指向乙個thunk而不是乙個虛函式實體的位址。

書中紛雜的講到不少中種情況,但我以我的理解,做如下小結:

多繼承下的虛函式,影響到虛函式的呼叫的實際質上為this的調整。而this調整一般為兩種:

1. 調整指標指向對應的subobject,一般發生在繼承類型別指標向基類型別指標賦值的情況下。

2. 將指向subobject的指標調整回繼承類物件的起始點,一般發生在基類指針對繼承類虛函式進行呼叫的時候。

第一點,使得該基類指標指向乙個與其指標型別匹配的子物件,唯有如此才能保證使得該指標在執行與其指標型別相匹配的特定行為的正確性。比方呼叫基類的成員,獲得正確的虛函式位址。可以想象如果不調整,用ptr訪問base2 subobject的資料成員時,會發生什麼?

呼叫base2的成員函式的時候,其成員函式接受的this指標指向derived 型別物件,這又會發生什麼?結果是整個物件的記憶體結構有可能都被破壞。還有別忘了,vptr也可以看做乙個資料成員,要找到虛函式,前提是獲取正確的vptr偏移量。

而第二點,顯然是讓乙個繼承類的虛函式獲取乙個正確的this指標,因為乙個繼承類虛函式要的是乙個指向繼承類物件的this指標,而不是指向其子物件。

第一順序繼承類之所以不需要進行調整的關鍵在於,其subobject的起點與繼承類物件的起點一致。

虛擬繼承下的虛函式

lippman說,如果乙個虛基類派生自另一虛基類,而且它們都支援虛函式和非靜態資料成員的時候,編譯器對虛基類的支援就像迷宮一樣複雜。其實我原想告訴他,我是懷著一顆勇士之心而來的。

雖然書中沒有介紹太多,但不難猜測的是在虛繼承情況下,複雜點在仍舊在於this指標的調整,然而其複雜度顯然又在多繼承之上,因為又多了乙個vbptr了。

1. 消極多型與積極多型之於我來說是乙個新的概念,在《深度探索c++物件模型》中也並沒有給出明確的定義,在網上也沒有看到關於它們對它們的明確定義。但我根據書中前後文及例子,推測lippman所說的是這樣的意思——根據是否為執行期多型來判斷是積極多型還是消極多型。

C虛函式表的工作原理

c 中的虛函式的作用主要是實現了多型的機制。關於多型,簡而言之就是用父型別別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。這種技術可以讓父類的指標有 多種形態 這是一種泛型技術。所謂泛型技術,說白了就是試圖使用不變的 來實現可變的演算法。比如 模板技術,rtti技術,虛函式技術,...

實業不能承受之虛

字型大小 小中大 如果你是一家快消品實業公司的負責人,嗯,我們往俗裡說,假裝你就是做飲料賣飲料的 就茶飲料好了。話說你有一款茶飲料,這產品不僅開創了乙個品類,而且早已經占有該品類50 以上的份額。可是最近你鬱悶了,因為若干家競爭對手的同類產品份額在上公升,相對應的你的份額 了5 於是你找來若干調研資...

09虛函式與多型性

多型性與虛函式 1 1.多型性概念 1 2.虛函式 2 2.1.虛函式的概念和使用方法 2 2.2.靜態關聯 早繫結 和動態關聯 晚繫結 2 2.3.虛函式表 3 2.4.虛析構函式 3 2.5.純虛函式和抽象類 4 2.6.構造基類成員函式的一般性原則 5 顧名思義,多型的意思是乙個事物有多種形態...