国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

C++:多態(tài)的底層實(shí)現(xiàn)原理 -- 虛函數(shù)表

這篇具有很好參考價(jià)值的文章主要介紹了C++:多態(tài)的底層實(shí)現(xiàn)原理 -- 虛函數(shù)表。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

目錄

一.?多態(tài)的原理

1.1?虛函數(shù)表

1.2?多態(tài)的實(shí)現(xiàn)原理

1.3?動(dòng)態(tài)綁定與靜態(tài)綁定

二.?多繼承中的虛函數(shù)表

2.1?虛函數(shù)表的打印

2.2?多繼承中虛函數(shù)表中的內(nèi)容存儲(chǔ)情況


一.?多態(tài)的原理

1.1?虛函數(shù)表

對(duì)于一個(gè)含有虛函數(shù)的的類,在實(shí)例化出來對(duì)象以后,對(duì)象所存儲(chǔ)的內(nèi)容包含兩部分:

  • 類的成員變量。
  • 一個(gè)指向虛函數(shù)表得虛函數(shù)表指針。

下段代碼定義了一個(gè)Base類,其中包含虛函數(shù)func1以及一個(gè)int型數(shù)據(jù),在main函數(shù)中,使用sizeof(Base)計(jì)算這個(gè)類實(shí)例化出來的對(duì)象大小為8bytes而不是4bytes,這正是因?yàn)?strong>虛函數(shù)表指針占了4bytes的存儲(chǔ)空間(32位編譯環(huán)境)。

class Base
{
public:
	virtual void func() { std::cout << "Base::func()" << std::endl; }

	int _b = 1;
};

int main()
{
	Base b;
	std::cout << sizeof(b) << std::endl;  //8
	return 0;
}

如果要調(diào)用Base中定義的虛函數(shù)func,那么程序會(huì)在運(yùn)行時(shí)根據(jù)虛函數(shù)指針找到虛函數(shù)表,虛函數(shù)表中存有函數(shù)指針(函數(shù)所在地址),程序會(huì)根據(jù)虛函數(shù)表中存儲(chǔ)的虛函數(shù)所在地址,找到對(duì)應(yīng)的函數(shù)進(jìn)行調(diào)用。

C++:多態(tài)的底層實(shí)現(xiàn)原理 -- 虛函數(shù)表
圖1.1?虛函數(shù)指針和虛函數(shù)表

1.2?多態(tài)的實(shí)現(xiàn)原理

多態(tài)的實(shí)現(xiàn),是通過虛函數(shù)的重寫來實(shí)現(xiàn)的。對(duì)于一個(gè)包含虛函數(shù)的基類Base,設(shè)有一派生類Derive繼承了基類Base,那么Derive會(huì)將Base的虛函數(shù)表一并繼承下來。

  • 如果Derive中沒有對(duì)Base中的虛函數(shù)進(jìn)行重寫,那么Derive和Base各自擁有不同的虛函數(shù)表,兩者虛函數(shù)表中存儲(chǔ)的內(nèi)容相同。
  • 如果Derive對(duì)Base的虛函數(shù)完成了重寫,那么虛函數(shù)表中的被重寫的虛函數(shù)的地址會(huì)被覆蓋,更新為派生類中對(duì)應(yīng)的虛函數(shù)地址。

演示代碼1.2中定義了一個(gè)基類Base和一個(gè)派生類Derive,Base中定義了兩個(gè)虛函數(shù)func1和func2,在派生類Derive中,func2被重寫了,func1沒有被重寫。運(yùn)行代碼,打開內(nèi)存監(jiān)視窗口,可以看到,在Derive對(duì)象的虛函數(shù)表中,func2的地址和Base對(duì)象中的不一樣,而func1的地址一樣,這證明了func2被重寫后,其記錄在虛函數(shù)表中的地址被覆蓋了。

演示代碼1.2:

#include<iostream>

class Base
{
public:
	virtual void func1() 
	{ 
		std::cout << "Base::func1()" << std::endl; 
	}

	virtual void func2()
	{
		std::cout << "Base::func2()" << std::endl;
	}
};

class Derive : public Base
{
public:
	virtual void func2()
	{
		std::cout << "Derive::func2()" << std::endl;
	}

	int _d = 1;
};

int main()
{
	Base b;
	Derive d;
	return 0;
}
C++:多態(tài)的底層實(shí)現(xiàn)原理 -- 虛函數(shù)表
圖1.2?繼承體系中的虛函數(shù)表及虛函數(shù)覆蓋情況

我們知道,多態(tài)的條件之一,就是通過父類的指針或引用去調(diào)用,可以從這個(gè)調(diào)用條件為切入點(diǎn),分析多態(tài)中函數(shù)調(diào)用的流程,來探索多態(tài)的底層原理,多態(tài)中函數(shù)調(diào)用流程為:

  1. 將父類或子類的對(duì)象(地址)賦值給父類的引用(指針)。
  2. 父類的對(duì)象或引用,根據(jù)其實(shí)際表示的對(duì)象或指向,拿到對(duì)應(yīng)的虛表函數(shù)指針,在虛函數(shù)表中找到要調(diào)用的虛函數(shù)地址,來調(diào)用對(duì)應(yīng)的函數(shù)。

正是由于子類對(duì)象中完成了對(duì)父類對(duì)象虛函數(shù)的重寫,所以在子類對(duì)象完成虛函數(shù)操作時(shí),會(huì)執(zhí)行子類中定義的虛函數(shù)。多態(tài)中的虛函數(shù)調(diào)用,是通過獲取虛函數(shù)表中的函數(shù)指針來確定具體調(diào)用哪個(gè)函數(shù)的,由于父類對(duì)象和子類對(duì)象的虛函數(shù)表中存儲(chǔ)不同的虛函數(shù)指針,所以會(huì)調(diào)用不同的虛函數(shù),從而實(shí)現(xiàn)了多態(tài)。

關(guān)于多態(tài)中虛函數(shù)表的生成和覆蓋,總結(jié)出以下幾點(diǎn)關(guān)鍵內(nèi)容:

  • 虛函數(shù)表本質(zhì)是一個(gè)存虛函數(shù)指針的指針數(shù)組,在VS編譯環(huán)境下,這個(gè)數(shù)組最后面放了一個(gè)nullptr,但是在Linux gcc編譯環(huán)境下,后面不會(huì)存有nullptr。
  • 派生類的虛表生成:a.先將基類中的虛表內(nèi)容拷貝一份到派生類虛表中? b.如果派生類重寫了基類中某個(gè)虛函數(shù),用派生類自己的虛函數(shù)覆蓋虛表中基類的虛函數(shù)? c.派生類自己新增加的虛函數(shù)按其在派生類中的聲明次序增加到派生類虛表的最后。
  • 虛函數(shù)表中存的是虛函數(shù)指針,而不是虛函數(shù)。虛函數(shù)和普通函數(shù)一樣,存儲(chǔ)在代碼段,對(duì)象中存儲(chǔ)的也不是虛函數(shù)表,而是虛函數(shù)表指針,其指向虛函數(shù)表所在的地址。

1.3?動(dòng)態(tài)綁定與靜態(tài)綁定

  • 靜態(tài)綁定:靜態(tài)綁定又稱為前期綁定,表示在程序編譯期間就可以確定程序完整的行為,函數(shù)的普通調(diào)用就是靜態(tài)綁定,在編譯時(shí),就能明確哪個(gè)函數(shù)會(huì)被調(diào)用,動(dòng)態(tài)綁定也可稱為編譯時(shí)決議。
  • 動(dòng)態(tài)綁定:在多態(tài)調(diào)用的場(chǎng)景下,需要在程序運(yùn)行時(shí),根據(jù)虛函數(shù)表中的函數(shù)地址,來確定調(diào)用哪個(gè)函數(shù),即:程序運(yùn)行起來之后才能明確程序的具體行為,動(dòng)態(tài)綁定也可稱為運(yùn)行時(shí)決議。
  • 函數(shù)普通調(diào)用為編譯時(shí)決議,函數(shù)的多態(tài)調(diào)用為運(yùn)行時(shí)決議。

二.?多繼承中的虛函數(shù)表

2.1?虛函數(shù)表的打印

為了探索多繼承中虛函數(shù)的行為,我們需要定義一個(gè)PrintVFTalbe函數(shù),來打印虛函數(shù)所存儲(chǔ)的地址,并通過虛函數(shù)表中存儲(chǔ)的函數(shù)指針調(diào)用對(duì)應(yīng)的函數(shù),來觀察虛函數(shù)表中的函數(shù)指針與子類和父類虛函數(shù)的指向關(guān)系。

因?yàn)?strong>VS編譯器會(huì)在虛函數(shù)表末尾位置存儲(chǔ)nullptr,所以使用Table[i] != nullptr作為循環(huán)結(jié)束的判斷條件(Linux?gcc編譯器不會(huì)將在虛函數(shù)表最后放nullptr,必須顯示地給定虛函數(shù)表中存儲(chǔ)的函數(shù)指針的個(gè)數(shù))。在每層循環(huán)內(nèi)部,先打印函數(shù)指針(函數(shù)首條指令地址),然后將函數(shù)指針變量賦值給ptr,通過函數(shù)指針調(diào)用函數(shù)。

演示代碼2.1:(虛函數(shù)表打印函數(shù))

typedef void (*VFPTR)();  //將指向無參數(shù)、返回void的函數(shù)的函數(shù)指針類型重定義為VFPTR

void PrintfVFTable(VFPTR* table)
{
	for (size_t i = 0; table[i] != nullptr; ++i)
	{
		printf("第%d個(gè)虛函數(shù)的地址:%p -> ", i, table[i]);
		VFPTR ptr = table[i];   //獲取函數(shù)指針
		ptr();   //通過函數(shù)指針調(diào)用函數(shù)
	}
	std::cout << std::endl;
}

2.2?多繼承中虛函數(shù)表中的內(nèi)容存儲(chǔ)情況

編寫演示代碼2.2,其中定義了兩個(gè)父類Base1和Base2,兩個(gè)父類中都定義了func1和func2虛函數(shù),并且,在子類Derive中,重寫func1函數(shù),并且定義了一個(gè)新的虛函數(shù)func3。

調(diào)試代碼,打開監(jiān)視窗口,我們可以發(fā)現(xiàn),子類對(duì)象中包含的兩個(gè)父類對(duì)象各有一張?zhí)摵瘮?shù)表,但是,VS的監(jiān)視窗口并沒有顯示出虛函數(shù)func3的地址,這并不是說func3的地址沒有進(jìn)虛函數(shù)表,而是VS編譯器沒有將其顯示出來,可以認(rèn)為這是編譯器的一個(gè)小BUG。

演示代碼2.2:

class Base1 
{
public:
	virtual void func1() { std::cout << "Base1::func1" << std::endl; }
	virtual void func2() { std::cout << "Base1::func2" << std::endl; }
private:
	int _b1 = 1;
};

class Base2 
{
public:
	virtual void func1() { std::cout << "Base2::func1" << std::endl; }
	virtual void func2() { std::cout << "Base2::func2" << std::endl; }
private:
	int _b2 = 2;
};

class Derive : public Base1, public Base2 
{
public:
	virtual void func1() { std::cout << "Derive::func1" << std::endl; }
	virtual void func3() { std::cout << "Derive::func3" << std::endl; }
private:
	int _d1 = 3;
};

int main()
{
	Derive d;
	Base1* ptr1 = &d;
	Base2* ptr2 = &d;

	PrintfVFTable((VFPTR*)*(int*)ptr1);  //打印Base1的虛函數(shù)表
	PrintfVFTable((VFPTR*)*(int*)ptr2);  //打印Base2的虛函數(shù)表

	return 0;
}
C++:多態(tài)的底層實(shí)現(xiàn)原理 -- 虛函數(shù)表
圖2.1 VS2019調(diào)試演示代碼2.2的監(jiān)視窗口

由于VS編譯器的這個(gè)小“bug”,就要求我們顯示的打印虛函數(shù)表,將虛函數(shù)指針作為參數(shù),傳給虛函數(shù)表打印函數(shù)??梢?。Base1的虛函數(shù)表中存儲(chǔ)了三個(gè)函數(shù)指針,從前到后依次為:子類定義的func1、Base1中定義的func2、func3,Base2的虛表中存儲(chǔ)了3個(gè)函數(shù)指針,從前到后依次為:子類中定義的func1、Base2的func2。

C++:多態(tài)的底層實(shí)現(xiàn)原理 -- 虛函數(shù)表
圖2.2? 多繼承體系中的虛表及虛表中存儲(chǔ)的內(nèi)容

根據(jù)圖2.2所示的虛表打印情況,總結(jié)出多繼承體系中如下的規(guī)律:文章來源地址http://www.zghlxwxcb.cn/news/detail-419927.html

  1. 在多繼承體系中,每一個(gè)基類都有一張?zhí)摫怼?/li>
  2. 如果兩個(gè)基類之中存在同名的虛函數(shù),同時(shí)在派生類中對(duì)同名的虛函數(shù)重寫,那么這兩個(gè)派生類中的虛函數(shù)都會(huì)被覆蓋。
  3. 派生類中未被重寫的虛函數(shù),會(huì)被存入第一個(gè)基類的虛表之中。
C++:多態(tài)的底層實(shí)現(xiàn)原理 -- 虛函數(shù)表
圖2.3?多繼承體系中的內(nèi)存模型

到了這里,關(guān)于C++:多態(tài)的底層實(shí)現(xiàn)原理 -- 虛函數(shù)表的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包