C++虛繼承的實(shí)現(xiàn)原理由內(nèi)存布局開始講起
準(zhǔn)備工作
1、VS2012使用命令行選項(xiàng)查看對(duì)象的內(nèi)存布局
微軟的Visual Studio提供給用戶顯示C++對(duì)象在內(nèi)存中的布局的選項(xiàng):/d1reportSingleClassLayout。使用方法很簡單,直接在[工具(T)]選項(xiàng)下找到“Visual Studio命令提示(C)”后點(diǎn)擊即可。切換到cpp文件所在目錄下輸入如下的命令即可
c1 [filename].cpp /d1reportSingleClassLayout[className]
其中[filename].cpp就是我們想要查看的class所在的cpp文件,[className]指我們想要查看的class的類名。(下面舉例說明...)
2、查看普通多繼承子類的內(nèi)存布局
既然我們今天講的是虛基類和虛繼承,我們就先用上面介紹的命令提示工具查看一下普通多繼承子類的內(nèi)存布局,可以跟后文虛繼承子類的內(nèi)存布局情況加以比較。
我們新建一個(gè)名叫NormalInheritance的cpp文件,輸入一下內(nèi)容。
/** 普通繼承(沒有使用虛基類) */ // 基類A class A { public: int dataA; }; class B : public A { public: int dataB; }; class C : public A { public: int dataC; }; class D : public B, public C { public: int dataD; };
上面是一個(gè)簡單的多繼承例子,我們啟動(dòng)Visual Studio命令提示功能,切換到NormalInheritance.cpp文件所在目錄,輸入一下命令:
c1 NormalInheritance.cpp /d1reportSingleClassLayoutD
我們可以看到class D的內(nèi)存布局如下:
從類D的內(nèi)存布局可以看到A派生出B和C,B和C中分別包含A的成員。再由B和C派生出D,此時(shí)D包含了B和C的成員。這樣D中就總共出現(xiàn)了2個(gè)A成員。大家注意到左邊的幾個(gè)數(shù)字,這幾個(gè)數(shù)字表明了D中各成員在D中排列的起始地址,D中的五個(gè)成員變量(B::dataA、dataB、C::dataA、dataC、dataD)各占用4個(gè)字節(jié),sizeof(D) = 20。
為了跟后文加以比較,我們?cè)賮砜纯碆和C的內(nèi)存布局:
虛繼承的內(nèi)存分布情況
上面我們看到了普通多繼承子類的內(nèi)存分布情況,下面我們進(jìn)入主題,來看看典型的菱形虛繼承子類的內(nèi)存分布情況。
我們新建一個(gè)名叫VirtualInheritance的cpp文件,輸入一下內(nèi)容:
/** 虛繼承(虛基類) */ #include <iostream> // 基類A class A { public: int dataA; }; class B : virtual public A { public: int dataB; }; class C : virtual public A { public: int dataC; }; class D : public B, public C { public: int dataD; };
VirtualInheritance.cpp和NormalInheritance.cpp的不同點(diǎn)在與C和C繼承A時(shí)使用了virtual關(guān)鍵字,也就是虛繼承。同樣,我們看看B、C、D類的內(nèi)存布局情況:
我們可以看到,菱形繼承體系中的子類在內(nèi)存布局上和普通多繼承體系中的子類類有很大的不一樣。對(duì)于類B和C,sizeof的值變成了12,除了包含類A的成員變量dataA外還多了一個(gè)指針vbptr,類D除了繼承B、C各自的成員變量dataB、dataA和自己的成員變量外,還有兩個(gè)分別屬于B、C的指針。
那么類D對(duì)象的內(nèi)存布局就變成如下的樣子:
- vbptr:繼承自父類B中的指針
- int dataB:繼承自父類B的成員變量
- vbptr:繼承自父類C的指針
- int dataC:繼承自父類C的成員變量
- int dataD:D自己的成員變量
- int A:繼承自父類A的成員變量
顯然,虛繼承之所以能夠?qū)崿F(xiàn)在多重派生子類中只保存一份共有基類的拷貝,關(guān)鍵在于vbptr指針。那vbptr到底指的是什么?又是如何實(shí)現(xiàn)虛繼承的呢?其實(shí)上面的類D內(nèi)存布局圖中已經(jīng)給出答案:
實(shí)際上,vbptr指的是虛基類表指針(virtual base table pointer),該指針指向了一個(gè)虛表(virtual table),虛表中記錄了vbptr與本類的偏移地址;第二項(xiàng)是vbptr到共有基類元素之間的偏移量。在這個(gè)例子中,類B中的vbptr指向了虛表D::$vbtable@B@,虛表表明公共基類A的成員變量dataA距離類B開始處的位移為20,這樣就找到了成員變量dataA,而虛繼承也不用像普通多繼承那樣維持著公共基類的兩份同樣的拷貝,節(jié)省了存儲(chǔ)空間。
為了進(jìn)一步確定上面的想法是否正確,我們可以寫一個(gè)簡單的程序加以驗(yàn)證:
int main() { D* d = new D; d->dataA = 10; d->dataB = 100; d->dataC = 1000; d->dataD = 10000; B* b = d; // 轉(zhuǎn)化為基類B C* c = d; // 轉(zhuǎn)化為基類C A* fromB = (B*) d; A* fromC = (C*) d; std::cout << "d address : " << d << std::endl; std::cout << "b address : " << b << std::endl; std::cout << "c address : " << c << std::endl; std::cout << "fromB address: " << fromB << std::endl; std::cout << "fromC address: " << fromC << std::endl; std::cout << std::endl; std::cout << "vbptr address: " << (int*)d << std::endl; std::cout << " [0] => " << *(int*)(*(int*)d) << std::endl; std::cout << " [1] => " << *(((int*)(*(int*)d)) + 1)<< std::endl; // 偏移量20 std::cout << "dataB value : " << *((int*)d + 1) << std::endl; std::cout << "vbptr address: " << ((int*)d + 2) << std::endl; std::cout << " [0] => " << *(int*)(*((int*)d + 2)) << std::endl; std::cout << " [1] => " << *((int*)(*((int*)d + 2)) + 1) << std::endl; // 偏移量12 std::cout << "dataC value : " << *((int*)d + 3) << std::endl; std::cout << "dataD value : " << *((int*)d + 4) << std::endl; std::cout << "dataA value : " << *((int*)d + 5) << std::endl; }
得到結(jié)果為:
到此這篇關(guān)于C++虛繼承的實(shí)現(xiàn)原理由內(nèi)存布局開始講起的文章就介紹到這了,更多相關(guān)C++虛繼承內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言中初始、增加和刪除進(jìn)程信號(hào)的操作方法簡介
這篇文章主要介紹了C語言中初始、增加和刪除進(jìn)程信號(hào)的操作方法簡介,分別是sigemptyset函數(shù)、sigaddset函數(shù)和sigdelset函數(shù)的用法,需要的朋友可以參考下2015-09-09C語言中回調(diào)函數(shù)的含義與使用場景詳解(2)
這篇文章主要為大家詳細(xì)介紹了C語言中回調(diào)函數(shù)的含義與使用場景,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03Linux網(wǎng)絡(luò)編程之UDP Socket程序示例
這篇文章主要介紹了Linux網(wǎng)絡(luò)編程之UDP Socket程序示例,有助于讀者在實(shí)踐中掌握UDP協(xié)議的原理及應(yīng)用方法,需要的朋友可以參考下2014-08-08