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.参考资料

  1. Why do we need virtual functions in C++?
  2. [面试经]VPTR和VTBL
  3. c++对象切割(Object Slicing)
  4. C++ 之 多态
  5. 虚指针、虚表及内存布局(不错)

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

  1. C++虚函数实现机制

    C++虚函数实现机制 C++程序的内存格局通常分为四个区:全局数据区,代码区,栈区,和堆区(即自由存储区). 全局数据区存放全局变量,静态数据和常量:所有类成员函数和非成员函数代码存放在代码区:为运行 ...

  2. 虚函数,虚函数表,虚函数实现原理,虚函数实现机制,虚函数解决的问题

    虚函数 虚函数表 虚函数实现原理? 虚函数解决的问题?虚函数解决问题的实现机制? 虚函数模型的构建? 虚函数模型的应用?

  3. C++ 虚函数实现机制

    转 C++面试题之虚函数(表)实现机制 前言 大家都应该知道C++的精髓是虚函数吧? 虚函数带来的好处就是: 可以定义一个基类的指针, 其指向一个继承类, 当通过基类的指针去调用函数时, 可以在运行时 ...

  4. C++中虚函数工作原理和(虚)继承类的内存占用大小计算

    转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7883531 一.虚函数的工作原理       虚函数的实现要求对象携带额 ...

  5. C++学习笔记——虚函数

    2019独角兽企业重金招聘Python工程师标准>>> 基本概念 虚函数是在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为: virtual ...

  6. 虚函数与虚函数表剖析(动多态)

    探索C++虚函数在g++中的实现 本文是我在追查一个诡异core问题的过程中收获的一点心得,把公司项目相关的背景和特定条件去掉后,仅取其中通用的C++虚函数实现部分知识记录于此. 在开始之前,原谅我先 ...

  7. 探索C++虚函数在g++中的实现

    本文是我在追查一个诡异core问题的过程中收获的一点心得,把公司项目相关的背景和特定条件去掉后,仅取其中通用的C++虚函数实现部分知识记录于此. 在开始之前,原谅我先借用一张图黑一下C++: &quo ...

  8. 在C++中用虚函数的作用是什么? 为什么要用到虚函数?

    ***************************************************更多精彩,欢迎进入:http://shop115376623.taobao.com******** ...

  9. c 语言中虚方法有什么作用是什么,虚函数的作用?

    定义 定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数[1] 语法:virtual 函数返回类型 函数名(参数表) {函数体;} 用途:实现多态性,通过指向派生类的 ...

最新文章

  1. opencv resize (C/C++/Python)
  2. Android camera开发总结
  3. 探索Oracle之数据库升级八 12c Downgrade 11gR2
  4. bzoj1049[HAOI2006]数字序列
  5. 登录页面(通过数据库查询密码是否正确)
  6. LeetCode - 9. 回文数
  7. 用python计算100以内的素数_python如何求100以内的素数
  8. 第一章:Ruby 安装 - Windows
  9. 使用BoundsChecker检测内存泄漏
  10. 办公技巧:腾讯文档怎么固定表头?
  11. html加页面脚注,javascript – 打印HTML每页脚注
  12. Oracle RAC原理
  13. 「面向对象程序设计-C++」学习笔记(下半部分)
  14. 【考前冲刺】计算机三级网络技术之综合题-IP地址计算
  15. 计算机控制面板包含的管理类别有什么,如何设置控制面板分类
  16. Mate 50,来了!
  17. 软件开发及计算机基础
  18. 运动爱好者的专属耳机,轻巧时尚又好用,哈氪无界上手
  19. PPTP 服务器配置
  20. Android数据库选哪个,DBFlow—目前最好用的安卓数据库

热门文章

  1. 互联网创业必须知道的几个名词:蝴蝶效应、青蛙现象、鳄鱼法则……
  2. 幼儿园小班计算机教案,幼儿园小班安全教案7篇
  3. 冯.诺依曼体系结构对计算机发展的限制
  4. Android 打开手机淘宝,并自动识别淘口令,弹出商品信息
  5. X87 FPU 指令集
  6. Linux SPI 驱动
  7. linux 游戏下载论坛,LINUX下的各种游戏
  8. 独特的电子邮箱地址-LeetCode练习(Java实现)
  9. java int溢出,结果只会保留低32位,高位会抛弃掉
  10. beyond compare 4官方中文版下载