日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

(一图胜千言)虚函数实现机制(Vptr, Vtbl)

發布時間:2023/12/8 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (一图胜千言)虚函数实现机制(Vptr, Vtbl) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 摘要

  • 講解C++中虛函數的實現機制,主要是Vptr和Vtbl的講解,有了虛函數才可以擁有像多態這種強大的功能。
  • 虛函數主要是出現在類的繼承體系中。

2.虛指針vptr和虛表vtbl

虛指針及虛表的概念(來自參考資料5)
首先要清楚,所謂指針其實質就是一個內存地址值,形如0x12345678;其次,要知道,函數名本身就是一個地址。
虛指針:其實就是一個地址值,以該地址為起始地址的一片內存單元存放著各虛函數的入口地址,這一片內存單元合起來就稱為虛函數表(想象一下:一片內存單元存著許多函數地址,想執行哪個虛函數就來這片內存單元查找該虛函數的入口地址,就像查表一樣,故稱虛函數表)。經過以上解釋,可以發現,所謂虛指針,就是個指向指針的指針。

2.1. 從上圖可以看出:

  • 基類和派生類的第一個條目存放的是虛指針,虛指針指的是虛表vtbl的起始地址,虛表中存放的是虛函數的入口地址,可以通過函數指針獲取到對應的函數去執行。
  • 相關的代碼見下節。

3. 相關代碼

#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)會涉及到內存對齊,所以得到的字節數可能不是你想的數字,比如int a,b;和int a;可能都是占8個字節。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位系統,指針為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; }

程序的輸出結果為:

------------------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后即使使用的是同一個接口,會根據對象的不同自動變換,對于各自的接口會在虛表中找到函數的入口地址,這種多態的方式叫“動態綁定”。
注意:多態的實現是通過指針和引用;而對象的轉換只會造成對象切割,不能實現多態。

4.參考資料

  • Why do we need virtual functions in C++?
  • [面試經]VPTR和VTBL
  • c++對象切割(Object Slicing)
  • C++ 之 多態
  • 虛指針、虛表及內存布局(不錯)
  • 總結

    以上是生活随笔為你收集整理的(一图胜千言)虚函数实现机制(Vptr, Vtbl)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。