C++: 如何用C语言实现C++的虚函数机制?

前言

googletest的源码中,看到gtest-matchers.h 中实现的MatcherBase 类自定义了一个 VTable,这种设计实现了一种类似于C++虚函数的机制。C++中的虚函数机制实质上就是通过这种方式实现的,本文用c语言自定义虚函数表VTable实现了一下virtual的功能,来深刻理解其机制。我们通过创建存储函数指针的结构体来模拟这种行为。

C++的运行时多态

如果我们在C++中有一个抽象基类 Shape ,定义了纯虚函数GetArea() 用于计算面积。对于不同的派生于 Shape 的类,面积计算方法会不一样。比如,对于圆形 Circle类,是 Shape 的一种,其特有属性为半径r,面积计算公式为 π·r² ;对于正方形 Square类,其特有属性为边长d,其面积计算公式为

基类Shape 的定义如下:

class Shape { 
public:
	virtual double GetArea()=0;
};

派生类Circle 继承于 Shape,定义如下:

class Circle: public Shape  {
public:
  Circle(double r)radius(r){}
	double GetArea() override {
		return radius * radius * 3.14;
	}
private:
	double radius;
};

通过基类指针指向不同派生类对象,去调用同一个方法,可以实现多态,如下所示:

Shape* shape = new CirCle(5);
shape->GetArea();

那么 virtual 实现多态的底层原理是什么呢?

用C语言简单实现virtual的底层原理

用C语言模拟实现以上C++代码。首先定义一个存储函数指针的结构体VTable,作为 Shape类的虚函数表 ,其中定义了两个函数指针, 分别指向该类计算面积的函数和析构函数,只要目标函数的参数列表和返回类型与函数指针定义相同,其中void*相当于this指针:

struct VTable{
    double (*GetArea)(void*);
    void (*Destructor)(void*);
};

然后定义一个基类Shape的结构体,其中包含了一个指向虚函数表VTable 的指针:

struct Shape{
    VTable* vtable;
};

在派生类 Circle 中,添加了额外的字段 radius,并且包含一个基类的实例,通过这种方式实现继承:

struct Circle{
    Shape base;
    double radius;
};

然后定义一个函数GetArea,作为公共调用接口,该函数接收一个Shape指针作为参数,并通过其指向类的虚函数表调用它的面积计算方法:

double GetArea(Shape* shape){
    return shape->vtable->GetArea(shape);
}

对于Circle类中的面积计算方法,实现如下:

double GetCircleArea(void* obj){
    Circle* circle = (Circle*)obj;
    return 3.14 * circle->radius * circle->radius;
}

最后,在程序中初始化Circle类的虚函数表 circle_vtable ,设置GetArea函数和析构函数,分配一块Circle对象大小的内存,将它的vtable绑定到circle_vtable ,初始化radius的值,并通过Shape类型的指针指向Circle对象,调用虚表中的方法:


VTable circle_vtable = {&GetCircleArea,
                        &CircleDestructor};

Circle* circle = (Circle*)malloc(sizeof(Circle));
circle->base.vtable = &circle_vtable;
circle->radius = 5;

Shape* shape = (Shape*)circle;
printf("Area of circle: %f\n", GetArea(shape));
ShapeDestructor(shape);

输出为:

Area of circle: 78.500000

对于Square 类,也是类似的实现。类设计如下图所示:

完整测试程序地址:https://compiler-explorer.com/z/zbGh7dsh4

自定义VTable的好处

通过virtual实现多态绝大多数时候都够了,那为什么googletest库中要自定义VTable呢?以下是一些好处,供参考,学习一下库开发者的思考角度。

  1. 更好的性能

C++的虚函数机制虽然方便,但是它在某些情况下会带来性能开销。例如,虚函数表的查找需要额外的时间,并且每个对象都需要一个指向虚表的指针,这会增加内存的开销。通过自定义的VTable机制,googletest 可以更好地控制这些开销,可能减少间接调用的开销,提高性能。

  1. 灵活的内存管理

使用自定义的VTable可以更灵活地管理内存。例如,可以将VTable实例放置在特定的内存区域或共享多个对象之间,从而减少内存占用。这种方式也可以使得一些轻量级对象不需要包含虚表指针,从而减小对象的大小。

  1. 跨编译器兼容性

不同的编译器和编译器版本对虚函数的实现可能略有不同,这会导致跨编译器的兼容性问题。通过自定义的VTable机制,googletest 可以避免依赖编译器的实现细节,保证在不同编译器和平台上的一致行为。

  1. 类型擦除和多态性

自定义的VTable机制可以实现类型擦除和更灵活的多态性。它允许将不同类型的对象统一处理,而不需要它们共享一个公共的基类。这对于模板编程和泛型编程非常有用,因为可以实现基于模板的多态而不需要依赖继承。

  1. 更好的调试和测试

自定义的VTable可以在调试和测试中提供更多的信息。例如,可以在VTable中包含额外的调试信息或断言,以帮助发现和诊断问题。这种灵活性在某些情况下是C++内置的虚函数机制所无法提供的。

总结

这个例子的实现对很多问题还没有考虑到,不过我认为它已经通过C语言基本展示了C++虚函数的原理。理解以上过程后,再去重新思考以下问题,可能会更清晰。

  1. C++ virtual运行时多态的实现原理?
  2. 派生类重写虚函数生效的条件是什么?
  3. 一个仅有虚析构函数的类大小为多少?
  4. 纯虚函数=0是什么含义?
  5. 虚函数为什么会稍慢些?其开销有哪些?
  6. 为什么构造函数不能是虚函数,而析构函数通常需要是虚函数?

参考

  1. https://github.com/google/googletest/blob/1d17ea141d2c11b8917d2c7d029f1c4e2b9769b2/googletest/include/gtest/gtest-matchers.h#L316
  2. https://stackoverflow.com/questions/78655663/why-does-matcherbase-class-in-gtest-matchers-h-define-a-vtable-and-what-is-its

如果你觉得本文对你有帮助,请点个赞,鼓励我持续创作;关注我,一起持续进步!

公众号:七昂的技术之旅

关注公众号,送你一份C++系列电子书。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/760095.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

等保主机测评防骗指南(资产调研)

你是否测评时常被运维给忽悠?是否觉得以下的对话耳熟? 你:您好,请问你们的主机资产有哪些,包括服务器、数据库、中间件、应用系统等。 甲:我们资产就这两台服务器,数据库什么的都这上面&#…

OpenGL3.3_C++_Windows(25)

阴影失真:阴影的不真实感 条纹样式: 首先理解采样原理:同光的视角下,渲染一张深度图,每个像素,存储同一射线下的深度值(不断更新深度缓冲的结果),即最近片段的深度。接着&#xff0…

hadoop词频统计

1 Hadoop 安装与伪分布的搭建 2 Hadoop词频统计 此文章基于搭建好hadoop之后做的词频统计实验,以上是链接为搭建hadoop的教程 目录 1 HDFS 文件系统常用命令 2 词频统计实验准备工作 2.1 启动hadoop 关闭防火墙 2.2 查看图形化界面 2.3 文件上传 3 词频统计…

isspace()方法——判断字符串是否只由空格组成

自学python如何成为大佬(目录): https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 isspace()方法用于判断字符串是否只由空格组成。isspace()方法的语法格式如下: str.isspace() 如果字符串中只包含空格&…

【Unity设计模式】✨使用 MVC 和 MVP 编程模式

前言 最近在学习Unity游戏设计模式,看到两本比较适合入门的书,一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》 这两本书介绍了大部分会使用到的设计模式,因此很值得学习 本…

【算法】5分钟了解如何使用PCA主成份分析

本文来自《老饼讲解-BP神经网络》https://www.bbbdata.com/ 目录 一、什么是PCA1.1.PCA的思想1.2.PCA的数学表示 二、什么是PCA的主成份与方差2.1.主成份的方差2.2.主成份的命名 三、如何使用PCA3.1.主成份的代码实现 主成份分析全称为PCA Principle Component Analysis ,它的主…

Linux虚拟串口设置

VSPD虚拟串口软件安装及使用 一、软件安装 1、Configure Virtual Serial Port Driver(VSPD) 1.1 首先下载 Configure Virtual Serial Port Driver(VSPD) 软件 链接:https://pan.baidu.com/s/11aGc2aHGUew5QZ0XhaWXJw 提取码:rmd7 1.2 安装时注意将…

计算机基础之汇编语言学习笔记

学习来源:b站各种学习资料 前置知识:计算机组成原理等知识 学习参考的资源 汇编语言编程的速成指南[上]~从零开始的期末抢救计划 (8086汇编)_哔哩哔哩_bilibili 链接: https://pan.baidu.com/s/1tg_ZW7VD3TS_s1v_EjS89w?pwdak6…

2029年AI服务器出货量将突破450万台,AI推理服务器即将爆发式增长

在2020年,新冠疫情与远程办公模式的兴起推动了所有类型服务器的出货量达到峰值,随后几年里,除了AI服务器之外的所有类别都回归到了正常水平。 根据Omdia的研究数据,AI服务器的出货量在2020年急剧上升,并且至今未显示出…

运筹系列93:VRP精确算法

1. 基础版本 定义 x i j k x_{ijk} xijk​为边 i j ij ij是否由车辆 k k k去运输。如果有时间窗约束的话,再加上一个变量 c i k c_{ik} cik​即可,表示第k辆车到达节点i时的时间点。 第一类客户流量约束,要求每个点都有1个入度和1个出度&…

ios13多窗口(UIWindowScene)学习笔记

ios13引入了UIWindowScene类、UIWindowSceneDelegate协议以便支持多窗口功能,但其适用于ipad,不适用于iphone,因为iphone不支持多窗口功能。注意,这里说的窗口不是UIWindow,而是UIWindowScene。 ios13前后的app的UI架…

AI陪伴产品的情感设计:从孤独感到恋爱感评分:9/10

本文主要阐述三个话题: 1. 市面上有哪些AI陪伴产品? 2. 我们团队要怎么做? 3. 为什么要做? 市面上有哪些陪伴类产品? Role-play(角色扮演) 在当前市场上,有不少以角色扮演为核心的…

Wails 安装初体验

文章目录 Wails 安装说明1. 系统要求2. 安装步骤3. 构建应用 结论 Wails 安装说明 Wails 是一个用于构建桌面应用的 Go 框架,结合了现代前端技术。以下是安装步骤: 1. 系统要求 Go 1.16 或更高版本Node.js 和 npm可选:适用于 Windows、mac…

iconfont-阿里巴巴矢量图标库 在vue项目使用记录

官网地址:https://www.iconfont.cn/manage/index?manage_typemyprojects&projectId4539761 第一步: 下载资源 ->解压到项目文件夹 第二步 在项目中main.ts 或者main.js 引入资源 import //assets/iconfont/font/iconfont.js; import //assets…

java基础知识点全集

JAVA的所有知识点 一、基础的数组、数据类型、输入输出二、类与对象1. 三大特征(1) 封装(2)继承(3)多态 2. 类的实例化(1) 类通过NEW来创建(2) 类的继承&…

python解锁图片相似度的神奇力量

在这个信息爆炸的时代,图片成为了我们传递信息、表达情感和记录生活的重要方式。然而,面对海量的图片资源,如何快速准确地找到相似的图片,成为了一个亟待解决的问题。现在,让我们为您揭开图片相似度的神秘面纱,带您领略这一创新技术的魅力! 图片相似度技术,就像是一位…

【多媒体】Java实现MP4视频播放器【JavaFX】【音视频播放】

在Java中播放视频可以使用多种方案,最常见的是通过Swing组件JFrame和JLabel来嵌入JMF(Java Media Framework)或Xuggler。不过,JMF已经不再被推荐使用,而Xuggler是基于DirectX的,不适用于跨平台。而且上述方案都需要使用第三方库。…

医院管理系统带万字文档医院预约挂号管理系统基于spingboot和vue的前后端分离java项目java课程设计java毕业设计

文章目录 仓库管理系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档(9.9¥带走) 仓库管理系统 一、项目演示 医院管理系统 二、项目介绍 基于springbootvue的前后端分离医院管…

QListView自定义item(结合QSqlQueryModel)

QListView:绘制自定义List(一)——设置ItemDelegate_qt_繁星执着-开放原子开发者工作坊 (csdn.net) QListView自定义Item_qlistview 自定义item-CSDN博客 结合我写的上一篇文章: QTableView与QSqlQueryModel的简单使用-CSDN博客 这次尝试…

webStorm debug vue项目的两种方案

一、前言 本文将介绍通过webstorm对vue项目进行debugger调试的两种方案。 但是,不管通过那种方案,都无法达到类似后端idea调试的体验,感觉十分难受,不过,比起用console.log还是好一些。如果各位有更好的方案&#xf…