1. C++程序設(shè)計模型支持的三種程序設(shè)計模型:
1.1. 程序模型(procedural model进萄、OP)可以理解為過程化模型就像C一樣
char boy[] = "Tom";
char* p_son;
...
p_son = new char[strlen(boy) + 1];
strcpy(p_son,boy);
...
if(!strcmp(p_son,boy))
{
take_to_disneyland(boy);
}
可以看出面向過程模型就是分析出解決問題所需要的步驟味滞,然后用函數(shù)把這些步驟一步一步實現(xiàn),使用的時候一個一個依次調(diào)用就可以了朗涩,如上圖代碼所示,對字符串的處理分為申請內(nèi)存、復(fù)制數(shù)據(jù),比較數(shù)據(jù)差異幾個步驟畴博,每個步驟都需要手動調(diào)用C庫函數(shù),且步驟的順序是固定的兴革,可以把面向過程理解為自頂向下的編程,其特點為代碼容易理解蜜唾,主要是算法+數(shù)據(jù)結(jié)構(gòu)杂曲,確定也顯而易見,對于復(fù)雜系統(tǒng)袁余,難以復(fù)用
1.2. 抽象數(shù)據(jù)類型模型(abstract data type model擎勘、ADT) 可以理解為非多態(tài)的數(shù)據(jù)封裝模型,和OO相比擁有更快的速度而且空間更緊湊(因為不需要virtual)颖榜。例如CString ,string 類
string boy = "Tom";
string son;
...
//string::operator=()
son = boy;
...
//string::operator==()
if(son == boy)
take_to_disneyland(boy);
可以看到ADT模型這里對字符串進行了封裝棚饵,并對外提供了一系列接口,這便是抽象的過程掩完,對字符串的處理不再需要手動地去使用C庫函數(shù)噪漾,取而代之的是一系列諸如“=” ,“==”的針對字符串類的抽象符號重載且蓬,這便是高度實物的抽象化欣硼,可復(fù)用性明顯增強了
1.3. 面向?qū)ο竽P停╫bject-oriented model、OO)
void check_in(Library_materials* p_mat)
{
if(p_mat->late())
p_mat->fine();
p_mat->check_in();
if(Lender* p_lend = p_mat->reserved())
p_mat->notify(p_lend);
}
Library_materials即為抽象的base class(用以提供共通接口)恶阴,真正的subtype例如Book诈胜、Video豹障、Laptop等等都可以從它派生而來,其特點為集齊了封裝焦匈、抽象血公、繼承、多態(tài)特點缓熟,適用大型復(fù)雜系統(tǒng)且易復(fù)用
2. C++對于程序模型的設(shè)計
早期的C++對象模型設(shè)計并不是一蹴而就的累魔,而是經(jīng)過一系列的對比選擇、優(yōu)化等流程荚虚,首先我們來看下面一個例子來看幾種可能的模型薛夜,對于class Point類
class Point
{
public:
Point(float val);
virtual ~Point();
float x() const;
static int PointCount();
protected:
virtual ostream& print(ostream& os) const;
float m_x;
static int m_iPointCount;
}
Point存在static 、nonstatic data member版述;static 梯澜、nonstatic 、virtual function member渴析,其在運行時會是怎樣的表現(xiàn)呢晚伙,編譯器如何模塑出這個類的data member呢和function members呢,首先是一種較為簡單的實現(xiàn)
2.1. 簡單對象模型(A Simple Object Model)
簡單對象模型在內(nèi)存上的結(jié)構(gòu)十分簡潔俭茧,一個類的成員函數(shù)和成員變量都有自己的一個slot(可以理解為槽)咆疗,每一個slot相當(dāng)于一個指針,指向?qū)?yīng)的函數(shù)或變量母债,內(nèi)存連續(xù)分布午磁,這種設(shè)計可以讓編譯器做更少的事,且類的內(nèi)存存放的都是同一種類型毡们,因此也較容易管理(類中的成員可以用slot的索引值來尋址迅皇,指針大小*索引),但是顯而易見的是衙熔,空間和執(zhí)行期的效率較低登颓,畢竟函數(shù)在內(nèi)存布局中也占有一個槽,且成員變量的每次訪問都需要經(jīng)由slot去跳轉(zhuǎn)地址訪問红氯,這個模型沒有應(yīng)用到C++實際的編譯器產(chǎn)品中框咙,但是其根據(jù)slot索引去尋址的觀念,被應(yīng)用到C++的"指向成員的指針"觀念中痢甘。
class CDerive{
public :
int m_iBase, m_iDervie;
}
//pmInt指針為數(shù)據(jù)成員指針喇嘱,可以指向CDerive對象的所有int數(shù)據(jù)成
//員 這里會給指針賦值4,即m_iDervie在class中的偏移量塞栅,這其實跟slot索引去尋址如出一轍
int (CDerive::*pmInt) = &CDerive::m_iDervie;
002A1730 mov dword ptr [pmInt],4
CDerive Dervie;
002A1737 lea ecx,[Dervie]
002A173A call CDerive::CDerive (02A10A0h)
002A173F mov dword ptr [ebp-4],0
Dervie.*pmInt = 8;
002A1746 mov eax,dword ptr [pmInt]
002A1749 mov dword ptr Dervie[eax],8
2.2. 表格驅(qū)動對象模型(A Table-driven Object Model)
簡單對象模型實際上每個類的大小是不固定的(每個類的成員變量跟函數(shù)數(shù)量不確定婉称,slot數(shù)量不確定),為了將所有的類的所有對象都有一致的內(nèi)存表達方式,表格驅(qū)動對象模型出現(xiàn)了王暗,類本身只含有兩個指針(可以理解為slot)悔据,一個指針指向數(shù)據(jù)表,一個指向函數(shù)表俗壹,數(shù)據(jù)表本身包含數(shù)據(jù)科汗,函數(shù)表則包含多個slot,每個slot指向?qū)?yīng)的成員函數(shù)绷雏,這個模型也沒有實際應(yīng)用到C++編譯器上(成員變量訪問效率還是低头滔,且對于類的成員函數(shù)界定不明確),但是成員函數(shù)表這個觀念卻成為C++實現(xiàn)虛函數(shù)機制的一個有效方案涎显。
2.3. C++對象模型(The C++ Object Model)
Stroustrup(C++之父)當(dāng)初設(shè)計的C++對象模型是從簡單對象模型派生而來的坤检,空間上采取了折中的方法,非靜態(tài)成員變量直接存儲在類對象內(nèi)存中期吓,靜態(tài)成員變量及靜態(tài)早歇、非靜態(tài)非虛函數(shù)放在類對象內(nèi)存之外,即不會在內(nèi)存布局中存放指向函數(shù)的slot指針讨勤,這樣就可以使得非靜態(tài)成員變量訪問效率提高的同時內(nèi)存空間也在一個可以接受的范圍箭跳,對于剩下的虛函數(shù)部分,則由兩個步驟支持:
1.每一個類有多少虛函數(shù)潭千,就產(chǎn)生多少個指針谱姓,指向?qū)?yīng)的虛函數(shù)地址,這些指針順序放置于表格之中刨晴,這個表格即virtual table
2.每一個類對象會在內(nèi)存中添加相應(yīng)的指針屉来,指向相關(guān)的virtual table,這個指針通常被叫做vptr
3. 解釋各種情況下的C++模型構(gòu)建
上述只是簡單介紹了C++對象模型的選擇依據(jù)及大體結(jié)構(gòu)狈癞,事實上茄靠,對于不同的情況,模型的結(jié)構(gòu)也會有差異亿驾,大體可以分為單繼承嘹黔、多繼承账嚎、菱形繼承莫瞬,單一虛繼承、虛擬菱形繼承郭蕉。
3.1 單繼承
class CBase
{
public:
CBase()
{
printf("CBase\r\n");
}
virtual ~CBase()
{
printf("~CBase\r\n");
}
void SetNumber(int iNumber)
{
m_iBase = iNumber;
}
int GetNumber()
{
return m_iBase;
}
virtual void Show()
{
printf("CBase Show\r\n");
}
static void Print()
{
printf("~CBase Print\r\n");
}
public:
int m_iBase;
};
class CDerive : public CBase
{
public:
virtual ~CDerive()
{
printf("~CDerive\r\n");
}
virtual void say(){};
void ShowNumber(int iNumber)
{
SetNumber(iNumber);
m_iDervie = iNumber + 1;
printf("%d\r\n",GetNumber());
printf("%d\r\n",m_iDervie);
}
public:
int m_iDervie;
};
int main(int argc,char* argv[])
{
CDerive Dervie;
}
可以看到CBase有一個非靜態(tài)成員變量疼邀,兩個虛函數(shù),三個非靜態(tài)成員函數(shù)召锈,一個靜態(tài)成員函數(shù)旁振,若不考慮繼承關(guān)系,單獨的CBase內(nèi)存結(jié)構(gòu)如下圖所示
如前面所講的一致,單一類若存在虛函數(shù)拐袜,編譯器便會創(chuàng)建一個虛函數(shù)表吉嚣,表中指針分別指向?qū)?yīng)的虛函數(shù)地址(按照虛函數(shù)聲明順序依次對應(yīng)放置),使用一個指針(Vptr)存儲虛函數(shù)表地址蹬铺,其余的函數(shù)處于代碼段且不會占用到虛函數(shù)表的slot尝哆。
對于單繼承類CDerive ,其內(nèi)存結(jié)構(gòu)如下所示
CDerive 在CBase的基礎(chǔ)上多了一個m_iDerive變量(置于尾部)甜攀,對于虛函數(shù)表秋泄,我們可以認(rèn)為編譯器先復(fù)制了一份CBase的虛函數(shù)表,在它的基礎(chǔ)上找到那些被當(dāng)前類重寫的函數(shù)规阀,把對應(yīng)slot指向的地址改為重寫的函數(shù)地址恒序,也即上面的CDerive ::~CDerive ,此時編譯器發(fā)現(xiàn)當(dāng)前類還有一個自己的虛函數(shù),將虛函數(shù)表添加一項slot谁撼,指向該虛函數(shù)歧胁,然后把對象的Vptr指針指向新的虛函數(shù)表地址處,值得注意的是彤敛,類的所有對象共享一份虛函數(shù)表与帆,如上面所展示CBase虛函數(shù)表跟CDerive虛函數(shù)表,可以該類的所有對象共用墨榄,即同一類所有對象Vptr指向同一個虛函數(shù)表地址玄糟,虛函數(shù)表在msvc編譯環(huán)境下放置于常量段中,可以如下驗證:
可以看到對于同一個類產(chǎn)生的CDerive對象袄秩,Vptr指向的虛函數(shù)表地址是一致的阵翎,我們通過地址找到對應(yīng)內(nèi)存,可以看到之剧,虛函數(shù)表地址處開始的12個字節(jié)存儲的正是3個虛函數(shù)的地址(每個四字節(jié))郭卫,通過虛函數(shù)表記錄的虛函數(shù)地址我們可以通過反匯編工具找到對應(yīng)的代碼段位置。
3.2 多繼承(非菱形繼承)
class CSofa
{
public:
CSofa()
{
m_iColor = 2;
}
virtual ~CSofa()
{
printf("~CSofa\r\n");
}
virtual int GetColor()
{
return m_iColor;
}
virtual int SitDown()
{
return printf("CSofa SitDown\r\n");
}
protected:
int m_iColor;;
};
class CBed
{
public:
CBed()
{
m_iLength = 4;
m_iWidth = 5;
}
virtual ~CBed()
{
printf("~CBed\r\n");
}
virtual int GetArea()
{
return m_iLength * m_iWidth;
}
virtual int Sleep()
{
return printf("CBed Sleep\r\n");
}
protected:
int m_iLength;
int m_iWidth;
};
class CSofaBed : public CSofa, public CBed
{
public:
CSofaBed()
{
m_iHeight = 6;
}
virtual ~CSofaBed()
{
printf("~CSofaBed\r\n");
}
virtual int SitDown()
{
return printf("CSofaBed SitDown\r\n");
}
virtual int Sleep()
{
return printf("CSofaBed Sleep\r\n");
}
virtual int GetHeight()
{
return m_iHeight;
}
protected:
int m_iHeight;
};
int main(int argc, char* argv[])
{
CSofaBed sofaBed;
}
多繼承一個首要的問題是類的虛函數(shù)表需要幾個背稼,虛函數(shù)指針(Vptr)需要幾個,內(nèi)存如何布局贰军?
- 直接繼承的有虛函數(shù)的父類有多少個,便有多少個虛函數(shù)表蟹肘,也會有相應(yīng)數(shù)量的虛函數(shù)指針词疼,對于本子類新增的虛函數(shù),則統(tǒng)一放在第一個虛函數(shù)表中
-
內(nèi)存布局中帘腹,父類按照聲明順序排列
其對象模型如下圖所示:
可以看到非菱形多繼承在單繼承的基礎(chǔ)上對多個父類進行布局贰盗,對父類虛函數(shù)表‘’拷貝‘后‘’重寫’的步驟其實是一樣的,只是需要考慮本子類新增的虛函數(shù)放置的位置阳欲,目前的編譯器大多將其放于第一個基類(存在虛函數(shù)表)的虛函數(shù)表中
3.3 菱形繼承
菱形繼承也稱為鉆石型繼承或重復(fù)繼承舵盈,它指的是基類被某個派生類簡單重復(fù)繼承了多次陋率。這樣,派生類對象中擁有多份基類實例
#include "stdio.h"
class CFurniture
{
public:
CFurniture()
{
m_iPrice = 0;
}
virtual ~CFurniture()
{
printf("~CFurniture()\r\n");
}
virtual int GetPrice()
{
return printf("CFurniture GetPrice\r\n");
}
public:
int m_iPrice;
};
class CSofa : public CFurniture
{
public:
CSofa()
{
m_iPrice = 1;
m_iColor = 2;
}
virtual ~CSofa()
{
printf("~CSofa\r\n");
}
virtual int GetColor()
{
return printf("CSofa GetColor\r\n");
}
virtual int SitDown()
{
return printf("CSofa SitDown\r\n");
}
protected:
int m_iColor;
};
class CBed : public CFurniture
{
public:
CBed()
{
m_iPrice = 3;
m_iLength = 4;
m_iWidth = 5;
}
virtual ~CBed()
{
printf("~CBed\r\n");
}
virtual int GetArea()
{
return printf("CBed GetArea\r\n");
}
virtual int Sleep()
{
return printf("CBed Sleep\r\n");
}
protected:
int m_iLength;
int m_iWidth;
};
class CSofaBed : public CSofa, public CBed
{
public:
CSofaBed()
{
m_iHeight = 6;
}
virtual ~CSofaBed()
{
printf("~CSofaBed\r\n");
}
virtual int SitDown()
{
return printf("CSofaBed SitDown\r\n");
}
virtual int Sleep()
{
return printf("CSofaBed Sleep\r\n");
}
virtual int GetHeight()
{
return printf("CSofaBed GetHeight\r\n");
}
protected:
int m_iHeight;
};
int main(int argc, char* argv[])
{
CSofaBed sofaBed;
sofaBed.m_iPrice = 1;//error
sofaBed.CSofa::m_iPrice = 1;//success
sofaBed.CBed::m_iPrice = 1;//success
}
其內(nèi)存布局如下圖所示:
可以看到由于CSofaBed 間接繼承了CFurniture兩次秽晚,導(dǎo)致內(nèi)存中有兩個m_iPrice數(shù)據(jù)成員瓦糟,增大了空間,也易引發(fā)歧義
3.4 簡單虛繼承
為了解決上述菱形繼承所帶來的空間及歧義問題赴蝇,虛繼承出現(xiàn)了狸页,在C++對象模型中,虛繼承而來的子類會生成一個隱藏的虛基類指針(vbptr)扯再,在Microsoft Visual C++中芍耘,虛基類表指針總是在虛函數(shù)表指針之后,因而熄阻,對某個類實例來說斋竞,如果它有虛基類指針,那么虛基類指針可能在實例的0字節(jié)偏移處(該類沒有vptr時秃殉,vbptr就處于類實例內(nèi)存布局的最前面坝初,否則vptr處于類實例內(nèi)存布局的最前面),也可能在類實例的4字節(jié)偏移處钾军。
一個類的虛基類指針指向的虛基類表鳄袍,與虛函數(shù)表一樣,虛基類表也由多個條目組成吏恭,條目中存放的是偏移值拗小。第一個條目存放虛基類表指針(vbptr)所在地址到該類內(nèi)存首地址的偏移值,由第一段的分析我們知道樱哼,這個偏移值為0(類沒有vptr)或者-4(類有虛函數(shù)哀九,此時有vptr)。我們通過一張圖來更好地理解搅幅。
class CFurniture
{
public:
CFurniture()
{
m_iPrice = 0;
}
virtual int GetPrice()
{
return printf("CFurniture GetPrice\r\n");;
}
public:
int m_iPrice;
};
class CSofaVirtual : virtual public CFurniture
{
public:
CSofaVirtual()
{
m_iPrice = 1;
m_iColor = 2;
}
virtual int GetColor()
{
return printf("CSofaVirtual GetColor\r\n");;
}
virtual int SitDown()
{
return printf("CSofaVirtual SitDown\r\n");
}
protected:
int m_iColor;
};
此時CSofaVirtual 的內(nèi)存模型如下圖所示:
我們使用指針對內(nèi)存進行訪問及打印輸出阅束,以便更好地理解模型
int main(int argc, char* argv[])
{
typedef void(*Fun)(void);
CSofaVirtual sofa;
cout << "CSofaVirtual對象內(nèi)存大小為:" << sizeof(CSofaVirtual) << endl;
//取得CSofaVirtual的虛函數(shù)表
cout << "[0]CSofaVirtual::vptr";
cout << "\t地址:" << (int *)(&sofa) << endl;
//輸出虛表CSofaVirtual::vptr中的函數(shù)
for (int i = 0; i<2; ++i)
{
cout << " [" << i << "]";
Fun fun1 = (Fun)*((int *)*(int *)(&sofa) + i);
fun1();
cout << "\t地址:\t" << *((int *)*(int *)(&sofa) + i) << endl;
}
cout << "[1]vbptr ";
cout << "\t地址:" << (int *)(&sofa) + 1 << endl; //虛表指針的地址
//輸出虛基類指針條目所指的內(nèi)容
for (int i = 0; i < 2; i++)
{
cout << " [" << i << "]";
cout << *(int *)((int *)*((int *)(&sofa) + 1) + i);
cout << endl;
}
//[2]
cout << "[2]CSofaVirtual::m_iColor=" << *(int*)((int *)(&sofa) + 2);
cout << "\t地址:" << (int *)(&sofa) + 2;
cout << endl;
cout << "-------------------------------------------------" << endl;
//[3]
cout << "[3]CFurniture::vptr";
cout << "\t地址:" << (int *)(&sofa) + 3 << endl;
//輸出CFurniture::vptr中的虛函數(shù)
for (int i = 0; i<1; ++i)
{
cout << " [" << i << "]";
Fun fun1 = (Fun)*((int *)*((int *)(&sofa) + 3) + i);
fun1();
cout << "\t地址:\t" << *((int *)*((int *)(&sofa) + 3) + i) << endl;
}
//[5]
cout << "[4]CFurniture::m_iPrice=" << *(int*)((int *)(&sofa) + 4);
cout << "\t地址: " << (int *)(&sofa) + 4;
cout << endl;
}
分析后我們可以得出,vbptr存儲的第二個字段(本例中為8)的數(shù)值即為基類CFurniture相對于vbptr的偏移值茄唐,當(dāng)需要訪問CFurniture的數(shù)據(jù)時息裸,通過vbptr存儲的偏移值去尋址。
3.5 虛擬菱形繼承
好了沪编,現(xiàn)在我們使用虛繼承來優(yōu)化菱形繼承
class CFurniture
{
public:
CFurniture()
{
m_iPrice = 0;
}
virtual int GetPrice()
{
return printf("CFurniture GetPrice\r\n");
}
protected:
int m_iPrice;
};
class CSofa :virtual public CFurniture
{
public:
CSofa()
{
m_iPrice = 1;
m_iColor = 2;
}
virtual int GetColor()
{
return printf("CSofa GetColor\r\n");
}
virtual int SitDown()
{
return printf("CSofa SitDown\r\n");
}
protected:
int m_iColor;;
};
class CBed :virtual public CFurniture
{
public:
CBed()
{
m_iPrice = 3;
m_iLength = 4;
m_iWidth = 5;
}
virtual int GetArea()
{
return printf("CBed GetArea\r\n");
}
virtual int Sleep()
{
return printf("CBed Sleep\r\n");
}
protected:
int m_iLength;
int m_iWidth;
};
class CSofaBed : public CSofa, public CBed
{
public:
CSofaBed()
{
m_iHeight = 6;
}
virtual int SitDown()
{
return printf("CSofaBed SitDown\r\n");
}
virtual int Sleep()
{
return printf("CSofaBed Sleep\r\n");
}
virtual int GetHeight()
{
return printf("CSofaBed GetHeight\r\n");
}
protected:
int m_iHeight;
};
其內(nèi)存布局如下圖所示:
對比單一虛繼承
可以看到與單一虛繼承相比呼盆,此例的虛擬菱形繼承多了次父類CBed跟最派生類CSofaBed,需要考慮的主要有兩點漾抬,一是次父類CBed跟CSofaBed內(nèi)存如何布局宿亡,二是CSofaBed類新增的虛函數(shù)應(yīng)該放置于哪張?zhí)摵瘮?shù)表
- 次父類按照聲明的順序順序放置常遂,最派生類放置于所有次父類之后纳令,最后是虛祖父類
- CSofaBed新增的虛函數(shù)放置于第一張?zhí)摵瘮?shù)表處
我們使用指針對內(nèi)存進行訪問及打印輸出,以便更好地理解模型
int main(int argc,char* argv[])
{
typedef void(*Fun)(void);
CSofaBed sofaBed;
cout << "sofaBed對象內(nèi)存大小為:" << sizeof(sofaBed) << endl;
//取得CSofa的虛函數(shù)表
cout << "[0]CSofa::vptr";
cout << "\t地址:" << (int *)(&sofaBed) << endl;
//輸出虛表CSofa::vptr中的函數(shù)
for (int i = 0; i<3; ++i)
{
cout << " [" << i << "]";
Fun fun1 = (Fun)*((int *)*(int *)(&sofaBed) + i);
fun1();
cout << "\t地址:\t" << *((int *)*(int *)(&sofaBed) + i) << endl;
}
//[1]
cout << "[1]CSofa::vbptr ";
cout << "\t地址:" << (int *)(&sofaBed) + 1 << endl; //虛表指針的地址
//輸出虛基類指針條目所指的內(nèi)容
for (int i = 0; i < 2; i++)
{
cout << " [" << i << "]";
cout << *(int *)((int *)*((int *)(&sofaBed) + 1) + i);
cout << endl;
}
//[2]
cout << "[2]CSofa::m_iColor=" << *(int*)((int *)(&sofaBed) + 2);
cout << "\t地址:" << (int *)(&sofaBed) + 2;
cout << endl;
cout << "-------------------------------------------------"<<endl;
//[3]
cout << "[3]CBed::vptr";
cout << "\t地址:" << (int *)(&sofaBed) + 3 << endl;
//輸出CBed::vptr中的虛函數(shù)
for (int i = 0; i<2; ++i)
{
cout << " [" << i << "]";
Fun fun1 = (Fun)*((int *)*((int *)(&sofaBed) + 3) + i);
fun1();
cout << "\t地址:\t" << *((int *)*((int *)(&sofaBed) + 3) + i) << endl;
}
//[4]
cout << "[4]CBed::vbptr ";
cout << "\t地址:" << (int *)(&sofaBed) + 4 << endl; //虛表指針的地址
//輸出虛基類指針條目所指的內(nèi)容
for (int i = 0; i < 2; i++)
{
cout << " [" << i << "]";
cout << *(int *)((int *)*((int *)(&sofaBed) + 4) + i);
cout << endl;
}
//[5]
cout << "[5]CBed::m_iLength=" << *(int*)((int *)(&sofaBed) + 5);
cout << "\t地址: " << (int *)(&sofaBed) + 5;
cout << endl;
//[6]
cout << "[6]CBed::m_iWidth=" << *(int*)((int *)(&sofaBed) + 6);
cout << "\t地址: " << (int *)(&sofaBed) + 6;
cout << endl;
cout << "-------------------------------------------------" << endl;
//[7]
cout << "[6]CSofaBed::m_iHeight=" << *(int*)((int *)(&sofaBed) + 7);
cout << "\t地址: " << (int *)(&sofaBed) + 7;
cout << endl;
cout << "-------------------------------------------------" << endl;
//[8]
cout << "[8]CFurniture::vptr";
cout << "\t地址:" << (int *)(&sofaBed) + 8 << endl;
//輸出CFurniture::vptr中的虛函數(shù)
for (int i = 0; i<1; ++i)
{
cout << " [" << i << "]";
Fun fun1 = (Fun)*((int *)*((int *)(&sofaBed) + 8) + i);
fun1();
cout << "\t地址:\t" << *((int *)*((int *)(&sofaBed) + 8) + i) << endl;
}
//[9]
cout << "[9]CFurniture::m_iPrice=" << *(int*)((int *)(&sofaBed) + 9);
cout << "\t地址: " << (int *)(&sofaBed) + 9;
cout << endl;
getchar();
}
可以看到,虛擬菱形繼承主要解決的便是class包含多個虛基類派生類產(chǎn)生的數(shù)據(jù)二義性及冗余的問題平绩,msvc編譯器采用的是虛基表存儲虛基類偏移的方式圈匆,虛基類成為一個共享部分,虛基類派生類訪問都通過虛基表指針去間接訪問捏雌,當(dāng)然跃赚,不同的編譯器為了支持虛擬菱形繼承所采用的方法也不盡相同,如Sun編譯器采用了虛函數(shù)表存儲虛基類偏移量的方式性湿,通過對虛函數(shù)表負(fù)索引得到偏移的方式進行間接訪問虛基類