(一图胜千言)虚函数实现机制(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)相关推荐
- C++虚函数实现机制
C++虚函数实现机制 C++程序的内存格局通常分为四个区:全局数据区,代码区,栈区,和堆区(即自由存储区). 全局数据区存放全局变量,静态数据和常量:所有类成员函数和非成员函数代码存放在代码区:为运行 ...
- 虚函数,虚函数表,虚函数实现原理,虚函数实现机制,虚函数解决的问题
虚函数 虚函数表 虚函数实现原理? 虚函数解决的问题?虚函数解决问题的实现机制? 虚函数模型的构建? 虚函数模型的应用?
- C++ 虚函数实现机制
转 C++面试题之虚函数(表)实现机制 前言 大家都应该知道C++的精髓是虚函数吧? 虚函数带来的好处就是: 可以定义一个基类的指针, 其指向一个继承类, 当通过基类的指针去调用函数时, 可以在运行时 ...
- C++中虚函数工作原理和(虚)继承类的内存占用大小计算
转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7883531 一.虚函数的工作原理 虚函数的实现要求对象携带额 ...
- C++学习笔记——虚函数
2019独角兽企业重金招聘Python工程师标准>>> 基本概念 虚函数是在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为: virtual ...
- 虚函数与虚函数表剖析(动多态)
探索C++虚函数在g++中的实现 本文是我在追查一个诡异core问题的过程中收获的一点心得,把公司项目相关的背景和特定条件去掉后,仅取其中通用的C++虚函数实现部分知识记录于此. 在开始之前,原谅我先 ...
- 探索C++虚函数在g++中的实现
本文是我在追查一个诡异core问题的过程中收获的一点心得,把公司项目相关的背景和特定条件去掉后,仅取其中通用的C++虚函数实现部分知识记录于此. 在开始之前,原谅我先借用一张图黑一下C++: &quo ...
- 在C++中用虚函数的作用是什么? 为什么要用到虚函数?
***************************************************更多精彩,欢迎进入:http://shop115376623.taobao.com******** ...
- c 语言中虚方法有什么作用是什么,虚函数的作用?
定义 定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数[1] 语法:virtual 函数返回类型 函数名(参数表) {函数体;} 用途:实现多态性,通过指向派生类的 ...
最新文章
- opencv resize (C/C++/Python)
- Android camera开发总结
- 探索Oracle之数据库升级八 12c Downgrade 11gR2
- bzoj1049[HAOI2006]数字序列
- 登录页面(通过数据库查询密码是否正确)
- LeetCode - 9. 回文数
- 用python计算100以内的素数_python如何求100以内的素数
- 第一章:Ruby 安装 - Windows
- 使用BoundsChecker检测内存泄漏
- 办公技巧:腾讯文档怎么固定表头?
- html加页面脚注,javascript – 打印HTML每页脚注
- Oracle RAC原理
- 「面向对象程序设计-C++」学习笔记(下半部分)
- 【考前冲刺】计算机三级网络技术之综合题-IP地址计算
- 计算机控制面板包含的管理类别有什么,如何设置控制面板分类
- Mate 50,来了!
- 软件开发及计算机基础
- 运动爱好者的专属耳机,轻巧时尚又好用,哈氪无界上手
- PPTP 服务器配置
- Android数据库选哪个,DBFlow—目前最好用的安卓数据库