虚函数表详解

article/2025/10/4 5:11:30

关键词:虚函数,虚表,虚表指针,动态绑定,多态

一、概述

为了实现C++的多态,C++使用了一种动态绑定的技术。这个技术的核心是虚函数表(下文简称虚表)。本文介绍虚函数表是如何实现动态绑定的。

二、类的虚表

每个包含了虚函数的类都包含一个虚表。 
我们知道,当一个类(A)继承另一个类(B)时,类A会继承类B的函数的调用权。所以如果一个基类包含了虚函数,那么其继承类也可调用这些虚函数,换句话说,一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表。

我们来看以下的代码。类A包含虚函数vfunc1,vfunc2,由于类A包含虚函数,故类A拥有一个虚表。

class A {
public:virtual void vfunc1();virtual void vfunc2();void func1();void func2();
private:int m_data1, m_data2;
};
  •  

类A的虚表如图1所示。 
这里写图片描述 
图1:类A的虚表示意图

虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。需要指出的是,普通的函数即非虚函数,其调用并不需要经过虚表,所以虚表的元素并不包括普通函数的函数指针。 
虚表内的条目,即虚函数指针的赋值发生在编译器的编译阶段,也就是说在代码的编译阶段,虚表就可以构造出来了。

三、虚表指针

虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可。同一个类的所有对象都使用同一个虚表。 
为了指定对象的虚表,对象内部包含一个虚表的指针,来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*__vptr,用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。

这里写图片描述
图2:对象与它的虚表

上面指出,一个继承类的基类如果包含虚函数,那个这个继承类也有拥有自己的虚表,故这个继承类的对象也包含一个虚表指针,用来指向它的虚表。

四、动态绑定

说到这里,大家一定会好奇C++是如何利用虚表和虚表指针来实现动态绑定的。我们先看下面的代码。

class A {
public:virtual void vfunc1();virtual void vfunc2();void func1();void func2();
private:int m_data1, m_data2;
};class B : public A {
public:virtual void vfunc1();void func1();
private:int m_data3;
};class C: public B {
public:virtual void vfunc2();void func2();
private:int m_data1, m_data4;
};
  •  

类A是基类,类B继承类A,类C又继承类B。类A,类B,类C,其对象模型如下图3所示。

这里写图片描述
图3:类A,类B,类C的对象模型

由于这三个类都有虚函数,故编译器为每个类都创建了一个虚表,即类A的虚表(A vtbl),类B的虚表(B vtbl),类C的虚表(C vtbl)。类A,类B,类C的对象都拥有一个虚表指针,*__vptr,用来指向自己所属类的虚表。 
类A包括两个虚函数,故A vtbl包含两个指针,分别指向A::vfunc1()和A::vfunc2()。 
类B继承于类A,故类B可以调用类A的函数,但由于类B重写了B::vfunc1()函数,故B vtbl的两个指针分别指向B::vfunc1()和A::vfunc2()。 
类C继承于类B,故类C可以调用类B的函数,但由于类C重写了C::vfunc2()函数,故C vtbl的两个指针分别指向B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()。 
虽然图3看起来有点复杂,但是只要抓住“对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向其继承的最近的一个类的虚函数”这个特点,便可以快速将这几个类的对象模型在自己的脑海中描绘出来。

非虚函数的调用不用经过虚表,故不需要虚表中的指针指向这些函数。

假设我们定义一个类B的对象。由于bObject是类B的一个对象,故bObject包含一个虚表指针,指向类B的虚表。

int main() 
{B bObject;
}
  • 现在,我们声明一个类A的指针p来指向对象bObject。虽然p是基类的指针只能指向基类的部分,但是虚表指针亦属于基类部分,所以p可以访问到对象bObject的虚表指针。bObject的虚表指针指向类B的虚表,所以p可以访问到B vtbl。如图3所示。
int main() 
{B bObject;A *p = & bObject;
}
  • 当我们使用p来调用vfunc1()函数时,会发生什么现象?
int main() 
{B bObject;A *p = & bObject;p->vfunc1();
}
  •  

程序在执行p->vfunc1()时,会发现p是个指针,且调用的函数是虚函数,接下来便会进行以下的步骤。 
首先,根据虚表指针p->__vptr来访问对象bObject对应的虚表。虽然指针p是基类A*类型,但是*__vptr也是基类的一部分,所以可以通过p->__vptr可以访问到对象对应的虚表。 
然后,在虚表中查找所调用的函数对应的条目。由于虚表在编译阶段就可以构造出来了,所以可以根据所调用的函数定位到虚表中的对应条目。对于 p->vfunc1()的调用,B vtbl的第一项即是vfunc1对应的条目。 
最后,根据虚表中找到的函数指针,调用函数。从图3可以看到,B vtbl的第一项指向B::vfunc1(),所以 p->vfunc1()实质会调用B::vfunc1()函数。

如果p指向类A的对象,情况又是怎么样?

int main() 
{A aObject;A *p = &aObject;p->vfunc1();
}
  •  

当aObject在创建时,它的虚表指针__vptr已设置为指向A vtbl,这样p->__vptr就指向A vtbl。vfunc1在A vtbl对应在条目指向了A::vfunc1()函数,所以 p->vfunc1()实质会调用A::vfunc1()函数。

可以把以上三个调用函数的步骤用以下表达式来表示:

(*(p->__vptr)[n])(p)

可以看到,通过使用这些虚函数表,即使使用的是基类的指针来调用函数,也可以达到正确调用运行中实际对象的虚函数。 
我们把经过虚表调用虚函数的过程称为动态绑定,其表现出来的现象称为运行时多态。动态绑定区别于传统的函数调用,传统的函数调用我们称之为静态绑定,即函数的调用在编译阶段就可以确定下来了。

那么,什么时候会执行函数的动态绑定?这需要符合以下三个条件。

  • 通过指针来调用函数
  • 指针upcast向上转型(继承类向基类的转换称为upcast,关于什么是upcast,可以参考本文的参考资料)
  • 调用的是虚函数

如果一个函数调用符合以上三个条件,编译器就会把该函数调用编译成动态绑定,其函数的调用过程走的是上述通过虚表的机制。

五、总结

封装,继承,多态是面向对象设计的三个特征,而多态可以说是面向对象设计的关键。C++通过虚函数表,实现了虚函数与对象的动态绑定,从而构建了C++面向对象程序设计的基石。

参考资料

  • 《C++ Primer》第三版,中文版,潘爱民等译
  • http://www.learncpp.com/cpp-tutorial/125-the-virtual-table/
  • 侯捷《C++最佳编程实践》视频,极客班,2015
  • Upcasting and Downcasting, http://www.bogotobogo.com/cplusplus/upcasting_downcasting.php

附录

示例代码


http://chatgpt.dhexx.cn/article/R9Udy8Ib.shtml

相关文章

虚函数及虚函数表

虚函数及虚函数表 各个类对象共享类的虚函数表,每个类对象有个虚函数指针vptr,虚函数指针vptr指向虚函数表(对于只有一个虚函数表的情况)。 虚函数 简单的说,每一个含有虚函数(无论是其本身的&#xff0…

简述虚函数表

前段时间我在博客中简单地说了下C的虚函数,所谓虚函数,就是C实现多态性的方法。那么编译器是如何识别虚函数的呢?据百度百科描述,C并未规定用何种方法实现虚函数,但是大部分编译器厂商都选择使用虚函数表这种方法&…

虚函数表的问题

虚函数表: 多态是由虚函数实现的,而虚函数主要是通过虚函数表(V-Table)来实现的。 如果一个类中包含虚函数(virtual修饰的函数),那么这个类就会包含一张虚函数表,虚函数表存储的每…

虚函数表详解及其应用场景

目录 概述1. 虚函数表概述2. 虚函数表的实现原理2.1. 虚函数的声明和定义2.2. 虚函数表的创建和初始化2.3. 虚函数调用的过程 3. 虚函数表的应用场景3.1. 多态性3.2. 基类指针和引用的使用3.3. 动态绑定3.4. 接口定义 结论 概述 在面向对象编程中,虚函数表&#xf…

C++ 面试必问:深入理解虚函数表

点击蓝字 关注我们 深入理解C 虚函数表 C中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。 Derive d; Base1 *b1 &d; Base2 *b2 &d; Base3 *b3 …

C++ 虚函数表

C类在内存中的存储方式 C 内存分为 5 个区域: 堆 heap :由 new 分配的内存块,其释放编译器不去管,由程序员自己控制。如果程序员没有释放掉,在程序结束时系统会自动回收。涉及的问题:“缓冲区溢出”、“内…

ThinkPHP3.2.2获取数据列getField()优化

getField()是一个常用方法,我习惯用来获取带key的数组,方便数据整合。 使用第1个参数,传入一个字段名,获取某一个数据值,返回满足条件的数据表中的该字段的第一行的值: $id M("User")->getFi…

java class getfield_java.lang.Class.getField()方法实例

全屏 java.lang.Class.getField() 返回一个Field对象,它反映此Class对象所表示的类或接口的指定公共成员字段。 name参数是一个字符串,指定所需字段的简单名称。 声明 以下是java.lang.Class.getField()方法的声明public Field getField(String name) th…

java getfield_Java 反射:通过 getField() 设置公共全局变量

Java 通过 getField() 操作公共全局变量 以前写 JavaWeb 项目启动初始化系统配置全局变量的代码,都是 variable Properties.getProperty(name) 这样一行一行代码的设置,变量少还好说,变量一多真的很磨叽。所以一直想通过 循环 简化代码&…

getField和getDeclaredField的区别

这两个方法都是用于获取字段 1.getField 只能获取public的,包括从父类继承来的字段。 2.getDeclaredField 可以获取本类所有的字段,包括private的,但是不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不…

vscode插件开发总结

一、关于vscode插件 相信大家对vscode应该都不陌生,VSCode是微软出的一款轻量级代码编辑器,免费而且功能强大,以功能强大、提示友好、不错的性能和颜值俘获了大量开发者的青睐,对JavaScript和NodeJS的支持非常好,自带…

2021-前端-VsCode插件

此乃吾习前端,VsCode之插件,个人所装,喜着自拿,不足之处还望海涵,多加批评。 1.Auto Close Tag——自动闭合尾部的标签 2.Atuo Rename Tag——修改 html 标签 自动帮你完成头部和尾部闭合标签的同步修改 3.Bracket…

关于VSCode插件的安装位置

VSCode的插件地址修改_上善若泪-CSDN博客_vscode插件位置文章目录1 data文件夹2 使用--extensions-dir命令3 使用mklink命令vscode编辑器强大的地方是可以使用各种各样的插件,但是插件默认的地方是在:C盘,让一些强迫症可能会受不了,非要迁移到…

vscode 插件-常用插件

VSCode常用插件(安装步骤同汉化) 1、*Auto Close Tag (自动闭合HTML/XML标签) 2、*Auto Rename Tag (自动帮你完成尾部闭合标签的同步修改,不过有些bug) 3、*Prettier(Prettier 是目前 Web 开发中最受欢迎的代码格式化程序) 安装Prettier -Code formatter这个插件…

Vscode 插件包下载并离线安装

打开VSCode插件官网 官网链接是https://marketplace.visualstudio.com/vscode 搜索Go 在输入框中输入go,搜索,结果如下: 点击Download Extension下载 注意:有时候找不到Download Extension,可能是网速加载慢&…

VsCode插件安装及推荐

1、快捷键Ctrl P,打开插件,输入 ext install (我习惯的输入方式); 2、或者点击图片中的圈红的按钮,也可以进入插件安装商城; 3、下面开始说下我目前安装的插件(我目前是vue开发&…

VScode安装离线插件

1. 下载及安装 首先在VScode官方插件库下载自己所需要的插件:https://marketplace.visualstudio.com/vscode 下载成功之后是以**.vsix**结尾的文件 然后再VScode软件中进行导入刚下载的文件 如果提示蓝色信息则为安装成功,红色则为失败 2. 版本不兼容报…

VSCode前端必备插件

跨平台的文本编辑器。由于其卓越的性能和丰富的功能,它很快就受到了大家的喜爱。 就像大多数 IDE 一样,VSCode 也有一个扩展和主题市场,包含了数以千计质量不同的插件。为了帮助大家挑选出值得下载的插件,我们针对性的收集了一些…

如何写一个vscode插件

1.运行yo code创建项目 2.选择使用yarn或者npm 3.运行 官网这个例子需要我们 ctrl shirt p 调出输入框, 然后在里面输入hello w 就可以如图所示 activationEvents: 当什么情况下, 去激活这个插件 activationEvents.onCommand: 在某个命令下激活(之后会专门列出很多其他条件…

Python好用的VSCode插件

1. Better Comments 这是一个让你能更好地编写注释的工具,它能根据关键词用不同的颜色高亮代码片段。支持以下类型的高亮: 感叹号 “!” 代码警告。问号“?”代表存留疑问。TODO 代码未来将要进行的操作。param 参数 2. autoDocstring 能够自动生成函…