C++中對象的內存佈局(三)

給出這樣的重複繼承:


(一)、直接繼承,沒有虛函數存在時,書寫如下:

class CA
{……};
class CB:public CA
{……};
class CC:public CA
{……};
class CD:public CB,public: CC
{……};

查看內存佈局如下:


由於B和C都繼承了A,所以在D中重複出現了A中的成員變量,所以當試圖訪問間接基類中的成員變量時,務必要加上作用域。

當把類CA、CB、CC、CD中的函數都寫成虛函數時,依舊還是重複出現,如下圖:


(二)、虛繼承

書寫代碼如下:(將繼承關係改爲虛繼承)

class CA
{
public:
	CA(int a=10):ma(a){}
	virtual void a(){cout<<"CA::a()"<<" ";}
	virtual void aa(){cout<<"CA::aa()"<<" ";}
	virtual void aaa(){cout<<"CA::aaa()"<<" ";}
public:
	int ma;
};

class CB:virtual public CA
{
public:
	CB(int b=20):mb(b){}
	virtual void a(){cout<<"CB::a()"<<" ";}
	virtual void bb(){cout<<"CB::bb()"<<" ";}
	virtual void bbb(){cout<<"CB::bbb()"<<" ";}
protected:
	int mb;
};
class CC:virtual public CA
{
public:
	CC(int c=30):mc(c){}
	virtual void a(){cout<<"CC::a()"<<" ";}
	virtual void cc(){cout<<"CC::cc()"<<" ";}
	virtual void ccc(){cout<<"CC::ccc()"<<" ";}
protected:
	int mc;
};
class CD:public CB,public CC
{
public:
	CD(int d):md(d){}
	virtual void a(){cout<<"CD::a()"<<" ";}
	virtual void bb(){cout<<"CD::bb()"<<" ";}
	virtual void ccc(){cout<<"CD::ccc()"<<" ";}
	virtual void d(){cout<<"CD::d()"<<" ";}
protected:
	int md;
};

對於虛繼承,可以避免重複繼承的作用,打印其內存佈局看看情況:


可以看到在虛繼承下,前面所說的出現兩次的成員變量和成員函數出現了一次,且都放在了內存佈局中的最後面,那怎麼找到呢?就是從圖中看到的vbptr指針,它指向vbtable,vbtable中記錄了兩行數字,據分析,可以做出總結:


根據以上內存佈局,做出打印:

typedef void (*Function)(void);//函數指針
int main()
{
	CD d(40);
	long** pd=(long**)(&d);//將對象d的地址強轉成爲二級指針。
	Function Fun=NULL;

	
	cout<<"[0]"<<" "<<"CB::_vfptr"<<endl;
	for(int i=0;i<3;++i)//打印類CB下的vfptr指向的vftable
	{
		Fun=((Function*)pd[0])[i];
		cout<<"    "<<"["<<i<<"]"<<" ";
		Fun();
		cout<<pd[0][i]<<endl;
	}
	cout<<"[1]"<<" "<<"CB::_vbptr"<<endl;
	for(int i=0;i<2;++i)//類CB下vbptr指向的vbtable
	{
		cout<<"       "<<pd[1][i]<<endl;
	}
	cout<<"[2] CB::mb="<<((long*)(&d))[2]<<endl;//CB::mb
	cout<<"[3]"<<" "<<"CC::_vfptr"<<endl;
	for(int i=0;i<2;++i)//打印類CC下的vfptr指向的vftable
	{
		Fun=((Function*)pd[3])[i];
		cout<<"    "<<"["<<i<<"]"<<" ";
		Fun();
		cout<<pd[3][i]<<endl;
	}
	cout<<"[4]"<<" "<<"CC::_vbptr"<<endl;
	for(int i=0;i<2;++i)//類CC下vbptr指向的vbtable
	{
		cout<<"       "<<pd[4][i]<<endl;
	}
	cout<<"[5] CC::mc="<<((long*)(&d))[5]<<endl;//CC::mc
	cout<<"[6] CD::md="<<((long*)(&d))[6]<<endl;//CD::md
	cout<<"[7] (vtordisp for vbase CA)="<<((long*)(&d))[7]<<endl;
	cout<<"[8]"<<" "<<"CA::_vfptr"<<endl;
	for(int i=0;i<3;++i)//類CA下vbptr指向的vbtable
	{
		cout<<"    "<<"["<<i<<"]"<<" ";
		Fun=((Function*)(pd[8]))[i];
		
		Fun();
		cout<<pd[8][i]<<endl;
	}
	cout<<"[9] CA::ma="<<((long*)(&d))[9]<<endl;//CA::ma

	return 0;
}

打印結果如下:


現在就可以畫出內存佈局:


總結:

1、當出現重複繼承時(像這種菱形繼承),會重複出現間接基類的數據,一般爲了避免重複出現,因此採用虛繼承(虛擬繼承)。

2、在虛擬繼承下,編譯器將重複出現的數據放在了這個內存佈局的最後邊(當然有vfptr時還是依據vfptr優先級高放),並且爲了標記它,在每個直接父類的作用域下有了vbptr指針,用來指向存放重複出現數據相對的偏移的vbtable。

3、在查看內存佈局時,看到了下標七號存放着一個0,也就是上面標記的vtordisp for vbase CA,爲什麼是0?不知道爲什麼,也沒查出結果,我猜想是爲了做區分吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章