2.4.2多重繼承下的虛函式
多重繼承下的虛函式主要有一下幾個麻煩:
1. 幾個父類都宣告了相同原型的virtual函式;
2. 有不止乙個父類將其析構函式宣告為虛擬;
3. 一般的虛函式問題;
先給出**段9。
class
parent1
virtual
~parent1()
virtual
void
speakclearly()
virtual
parent1
*clone
()const
protected:
intdata_parent1;
}; class
parent2
virtual
~parent2()
virtual
void
mumble()
virtual
parent2
*clone
()const
protected:
intdata_parent2;
}; class
child
:public
parent1
,public
parent2
virtual
~child()
virtual
child
*clone
()const
protected:
intdata_child;
}; 就記憶體布局而言,有了前面的基礎了,猜得出來大概是個什麼樣子了。好吧,我們就先猜一把,然後再寫段**驗證驗證。對於資料成員,多重繼承使用的就是各自分配一段空間「疊放」在一起,如之前的圖4所示。對於虛函式,其實就是多了個vptr嘛,也放進去不久結了嗎?
嗯,所以我們可以猜想了,見圖8。
接下來就是除錯驗證了,除錯**段
10如下:
typedef
void
(*fun
)(void);
intmain()
需要的是,這段**的執行要將虛析構函式注釋掉,理由應該很好理解吧,物件都被析構掉了,指標也就成為懸掛指標了,sigsegv就會觸發。code::blocks(gcc 4.5.2)下執行結果如下:
再次說明一下,因為我們注釋掉了虛析構函式那一行,所以上面的輸出中沒有child::~child()之類的資訊。所以,我們的猜想圖8是正確的。
根據《inside the c++ object model》一書,關於多重繼承主要有三種情況要仔細考慮,對著圖8,這三種情況其實都是浮雲。
1. 通過乙個「指向第二個父類,如parent2」的指標,呼叫子類的虛函式。請看**段11:
parent2
*pp2
=new
child;
// 下面的**將呼叫child::~child()
// 因此pp2必須被向後調整sizeof(parent1)個bytes,由編譯器和執行期資訊參與完成
delete
pp2;
還是回到圖8,注意到乙個問題,parent1和child指標所指向的位置是一樣的(如果都是取的同乙個child物件的位址),但是parent2不是,它與parent1和child的指標之間存在乙個偏移量,看下面的**就知道了:
child c;
parent1
*pp1 =&
c;parent2
*pp2 =&
c;child
*pc =&
c;cout
<<
pp1<<
"/n"
<<
pc<<
"/n"
<<
pp2<<
"/n/n";
執行結果如下圖:
pp1與pc內容一樣,pp2與pp1和pc之間存在8個位元組的偏移量(8個位元組是由乙個4位元組int變數和乙個4位元組指標引起的)。
對於**段11,因為pp2指向child物件中parent2子物件處,為了能夠正確執行,pp2必須調整到child物件起始處。
1. 通過乙個「指向child類」的指標,呼叫parent2中乙個繼承而來的虛函式。在這種情況下,子類指標必須再次被調整,以指向第二個父類parent2處。例如:
child
*pc
=new
child;
// 呼叫parent2::mumble()
// pc必須被向前調整sizeof(parent1)個bytes,由編譯器和執行期資訊參與完成
pc->
mumble
();
2. 第三種情況發生在乙個語言擴充性質之下:允許乙個虛函式的返回值型別有所變化(注意,返回值型別不是啟用c++過載機制的充分條件),可能是父類型別,也可能是子類型別,這一點通過clone()函式來描述,看下面**:
parent2
*pp1
=new
child;
// 呼叫child* child::clone()
// 返回值必須被調整,以指向parent2子物件,由編譯器和執行期資訊參與完成
parent2
*pp2
=pp1
->
clone
();
當進行pp1
->
clone
()時,pp1會被調整到指向child物件的起始位址,於是clone的child版會被呼叫,它會傳回乙個指標,指向乙個新的child物件,該物件的位址在被指定給pp2之前,必須經過調整,以指向parent2子物件處。
之前的注釋中都有一句話,「***必須被調整,以指向parent2子物件,由編譯器和執行期資訊參與完成」,確實,就是編譯器會去做的事情,我們也不用管,因為這也是compiler-dependent的。
2.4.3虛繼承下的虛函式
虛擬繼承的出現就是為了解決重複繼承中多個間接父類的問題的,經典繼承結構圖就是環形繼承鏈:
class
pp ;
class
p1 :
virtual
publicpp;
classp2:
virtual
publicpp;
classc :
publicp1,
publicp2;
1) 先是p1,然後是p2,接著是c,而pp這個超類都放在最後的位置;
2) 各個類內部的布局與多重繼承一樣;
3、c++物件模型總結
大道至簡,如果理解了前面的文字,下面四句話應該就差不多了:
l 非靜態資料成員都存放在物件所跨有的位址空間中,靜態資料成員則存放於物件所跨有的位址空間之外;
l 非虛擬成員函式(靜態和非靜態)也存放於物件所跨有的位址空間之外,且編譯器將其改寫為普通的非成員函式的形式(以求降低呼叫開銷);
l 對於虛擬成員函式,則借助vtbl和vptr支援。
l 對於繼承關係,子類物件跨有的位址空間中包含了父類物件的實體,通過嵌入type-info資訊進行識別和虛函式呼叫。
4、參考資料
[1] inside the c++ object model,lippaman,第1、2、4章。
[2] c++物件記憶體布局,陳皓專欄,
C 類物件成員變數與成員函式記憶體分配問題
了解c 類位址的存放和分配等問題,能幫助我們更深入 更清晰了解類的組成及其使用。自己目前不是很清楚,先收集一些網上資料,而後再慢慢補充增加的了解.網路收集之 關於結構體和c 類的記憶體位址問題 今天終於有時間寫點東西了 太爽了 很多人都知道c 類是由結構體發展得來的,所以他們的成員變數 c語言的結構...
C 類物件成員變數與成員函式記憶體分配問題
很多人都知道c 類是由結構體發展得來的,所以他們的成員變數 c語言的結構體只有成員變數 的記憶體分配機制是一樣的。下面我們以類來說明問題,如果類的問題通了,結構體也也就沒問題啦。類分為成員變數和成員函式,我們先來討論成員變數。乙個類物件的位址就是類所包含的這一片記憶體空間的首位址,這個首位址也就對應...
C 類物件成員變數與成員函式記憶體分配問題
很多人都知道c 類是由結構體發展得來的,所以他們的成員變數 c語言的結構體只有成員變數 的記憶體分配機制是一樣的。下面我們以類來說明問題,如果類的問題通了,結構體也也就沒問題啦。類分為成員變數和成員函式,我們先來討論成員變數。乙個類物件的位址就是類所包含的這一片記憶體空間的首位址,這個首位址也就對應...