(一图胜千言)虚函数实现机制(Vptr, Vtbl)
生活随笔
收集整理的這篇文章主要介紹了
(一图胜千言)虚函数实现机制(Vptr, Vtbl)
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
1. 摘要
- 講解C++中虛函數(shù)的實現(xiàn)機制,主要是Vptr和Vtbl的講解,有了虛函數(shù)才可以擁有像多態(tài)這種強大的功能。
- 虛函數(shù)主要是出現(xiàn)在類的繼承體系中。
2.虛指針vptr和虛表vtbl
虛指針及虛表的概念(來自參考資料5)
首先要清楚,所謂指針其實質(zhì)就是一個內(nèi)存地址值,形如0x12345678;其次,要知道,函數(shù)名本身就是一個地址。
虛指針:其實就是一個地址值,以該地址為起始地址的一片內(nèi)存單元存放著各虛函數(shù)的入口地址,這一片內(nèi)存單元合起來就稱為虛函數(shù)表(想象一下:一片內(nèi)存單元存著許多函數(shù)地址,想執(zhí)行哪個虛函數(shù)就來這片內(nèi)存單元查找該虛函數(shù)的入口地址,就像查表一樣,故稱虛函數(shù)表)。經(jīng)過以上解釋,可以發(fā)現(xiàn),所謂虛指針,就是個指向指針的指針。
2.1. 從上圖可以看出:
- 基類和派生類的第一個條目存放的是虛指針,虛指針指的是虛表vtbl的起始地址,虛表中存放的是虛函數(shù)的入口地址,可以通過函數(shù)指針獲取到對應(yīng)的函數(shù)去執(zhí)行。
- 相關(guān)的代碼見下節(jié)。
3. 相關(guān)代碼
#include <iostream>No virtual class/ class Animal { public:void eat() {std::cout << "I'm eating generic food." << std::endl;}void shout() {std::cout << "I'm shouting genericly." << std::endl;} };class Cat : public Animal { public:void eat() {std::cout << "I'm eating a rat." << std::endl;}void shout() {std::cout << "I'm shouting with meow" << std::endl;}};void distinguish(Animal* obj) {obj->eat();obj->shout(); } class with virtual/ class Animal_V { public:virtual void eat() {std::cout << "I'm eating generic food." << std::endl;}virtual void shout() {std::cout << "I'm shouting genericly." << std::endl;}virtual ~Animal_V() = default; };class Cat_V : public Animal_V { public:void eat() override {std::cout << "I'm eating a rat." << std::endl;}void shout() override {std::cout << "I'm shouting with meow" << std::endl;}~Cat_V() override = default; };void distinguish(Animal_V* obj) {obj->eat();obj->shout(); }int main(int argc, char* argv[]) {auto *animal = new Animal;auto *cat = new Cat;std::cout << "------------------Normal use------------------" << std::endl;animal->eat();animal->shout();cat->eat();cat->shout();std::cout << "------------------Use same interface without vitual------------------" << std::endl;distinguish(animal);distinguish(cat);std::cout << "==================Divider==================" << std::endl;auto *animal_V = new Animal_V;auto *cat_V = new Cat_V;std::cout << "------------------Normal use------------------" << std::endl;animal_V->eat();animal_V->shout();cat_V->eat();cat_V->shout();std::cout << "------------------Use same interface with vitual------------------" << std::endl;distinguish(animal_V);distinguish(cat_V);//由于sizeof(your class)會涉及到內(nèi)存對齊,所以得到的字節(jié)數(shù)可能不是你想的數(shù)字,比如int a,b;和int a;可能都是占8個字節(jié)。std::cout << "------------------Compare sizeof------------------" << std::endl;std::cout << "Animal'size is: " << sizeof(Animal) << std::endl;std::cout << "Cat'size is: " << sizeof(Cat) << std::endl;std::cout << "Animal_V'size is: " << sizeof(Animal_V) << std::endl;std::cout << "Cat_V'size is: " << sizeof(Cat_V) << std::endl;std::cout << "------------------The address of virtual function------------------" << std::endl;std::cout << "Animal_V::eat -> " << (void*)(&Animal_V::eat) << std::endl;std::cout << "Animal_V::shout -> " << (void*)(&Animal_V::shout) << std::endl;std::cout << "Cat_V::eat -> " << (void*)(&Cat_V::eat) << std::endl;std::cout << "Cat_V::shout -> " << (void*)(&Cat_V::shout) << std::endl;std::cout << "------------------The address of vptr&vtbl------------------" << std::endl;Animal_V obj;std::cout << "VPTR's address:" << (long*) (&obj+0) << std::endl; //由于我的電腦是64位系統(tǒng),指針為8個地址,所以使用long類型的指針獲取虛指針以及下面的虛表std::cout << "VTBL's address:" << (long*) (*(long*)(&obj+0)) << std::endl;std::cout << "The entry address of the first virtual function: " << (void*) (*((long*)*(long*)(&obj+0)+0)) << std::endl;std::cout << "The entry address of the second virtual function: " << (void*) (*((long*)*(long*)(&obj+0)+1)) << std::endl;// long* vptr_addr = (long*) &obj;// long* vtbl_addr = (long*) *vptr_addr;// std::cout << "The entry address of the first virtual function: " << (long*) *(vtbl_addr + 0) << std::endl;// std::cout << "The entry address of the second virtual function: " << (long*) *(vtbl_addr + 1) << std::endl;std::cout << "------------------Invoke virtual function by pFun------------------" << std::endl;typedef void(*pFun)(void);pFun Fun = nullptr;Fun = reinterpret_cast<pFun>(*((long*)*(long*)(&obj + 0) + 0));Fun();Fun = reinterpret_cast<pFun>(*((long*)*(long*)(&obj + 0) + 1));Fun();delete animal;delete cat;delete animal_V;delete cat_V;return 0; }程序的輸出結(jié)果為:
------------------Normal use------------------ I'm eating generic food. I'm shouting genericly. I'm eating a rat. I'm shouting with meow ------------------Use same interface without vitual------------------ I'm eating generic food. I'm shouting genericly. I'm eating generic food. I'm shouting genericly. ==================Divider================== ------------------Normal use------------------ I'm eating generic food. I'm shouting genericly. I'm eating a rat. I'm shouting with meow ------------------Use same interface with vitual------------------ I'm eating generic food. I'm shouting genericly. I'm eating a rat. I'm shouting with meow ------------------Compare sizeof------------------ Animal'size is: 1 Cat'size is: 1 Animal_V'size is: 8 Cat_V'size is: 8 ------------------The address of virtual function------------------ Animal_V::eat -> 0x4018c6 Animal_V::shout -> 0x4018f2 Cat_V::eat -> 0x40191e Cat_V::shout -> 0x40194a ------------------The address of vptr&vtbl------------------ VPTR's address:0x7ffcbb9e5bc8 VTBL's address:0x401e40 The entry address of the first virtual function: 0x4018c6 The entry address of the second virtual function: 0x4018f2 ------------------Invoke virtual function by pFun------------------ I'm eating generic food. I'm shouting genericly.可以看出使用virtual后即使使用的是同一個接口,會根據(jù)對象的不同自動變換,對于各自的接口會在虛表中找到函數(shù)的入口地址,這種多態(tài)的方式叫“動態(tài)綁定”。
注意:多態(tài)的實現(xiàn)是通過指針和引用;而對象的轉(zhuǎn)換只會造成對象切割,不能實現(xiàn)多態(tài)。
4.參考資料
總結(jié)
以上是生活随笔為你收集整理的(一图胜千言)虚函数实现机制(Vptr, Vtbl)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 642-825 认证题库
- 下一篇: 22 《巴黎文学散步地图》 -豆瓣评分7