C++对象模型(9)-- 虚函数1

对《深入C++对象模型》中的一个疑问——虚函数表的type_info的位置 vptr指向的第一个位置是第一个虚函数的地址,不是type_info 阅读详情

虚函数是指用 virtual 关键字修饰的类成员函数。其定义如下所示:

virtual void vir_print(){}

虚函数的作用是允许使用基类的指针来调用子类的函数,从而实现“多态”。含虚函数的类在编译时会产生一张虚函数表vtbl,里面保存了函数的调用地址。类对象通过虚函数表指针vptr找到虚函数表vtbl,通过vtbl找到对应函数的调用地址,然后调用执行。

1、虚函数表指针位置

一般地,虚函数表指针位于对象的开头位置。

我们可以写一段代码来测试一下:

class Base {
public:
    int t_i = 8;
    virtual void vir_print() {
        std::cout << "Base::vir_print()" << std::endl;
    }
};

int main()
{
    Base base;
    return 0;
}

我们用VS2019来调试上面的程序,把断点设在第12行的return 0处,运行后打开“快速监视”工具,在“表达式”窗口输入:&base。可以看到base对象的内存布局如下:

另外,我们也可以通过读取二进制的方式来验证base对象的内存模型。我们在main()函数中加入下面的代码:

std::cout << "sizeof(base) = " << sizeof(base) << std::endl;
int* pt_i = (int*)((char*)&base + 4);
std::cout << "t_i = " << *pt_i << std::endl;

程序的输出结果为:

sizeof(base) = 8

t_i = 8

所以对象的大小是8个字节,第5 - 8个字节是变量t_i,所以1-4个字节是虚函数表指针的。

2、虚函数的手工调用

为了加深对虚函数调用过程的认识,我们直接通过虚函数表指针来调用虚函数。

Base类的定义如下:

class Base {
public:
    int t_i = 8;
    virtual void vir_f() {
        std::cout << "Base::vir_f()" << std::endl;
    }

    virtual void vir_g() {
        std::cout << "Base::vir_g()" << std::endl;
    }

    virtual void vir_h() {
        std::cout << "Base::vir_h()" << std::endl;
    }
};

Base类对象的内存布局是下面这样的。

虚函数表和成员函数都是属于Base类的,虚函数表指针是属于base对象的,每个对象都会生成自己的虚函数表指针,但指针指向的虚函数表地址都相同。

main()函数的定义如下:

int main()
{
    Base base;
    long* pvptr = (long*)&base; //因为对象的首地址是虚函数表指针
    long* vptr = (long*)(*pvptr); //指针的值就是指向的虚函数表

    //定义函数指针,指向 void (void)函数
    typedef void (*Func)(void);

    //因为虚函数表是个数组,所以我们可以通过vptr[i]的方式来调用对应的虚函数
    Func vir_f = (Func)vptr[0];
    Func vir_g = (Func)vptr[1];
    Func vir_h = (Func)vptr[2];

    vir_f();
    vir_g();
    vir_h();
}

3、继承虚函数表分析

3.1 单继承

假设有Base, Derive两个类,其中Derive继承自Base类。代码如下所示:

class Base {
public:
    int b_i = 8;
    virtual void vir_f() {
        std::cout << "Base::vir_f()" << std::endl;
    }

    virtual void vir_g() {
        std::cout << "Base::vir_g()" << std::endl;
    }

    virtual void vir_h() {
        std::cout << "Base::vir_h()" << std::endl;
    }

    int d_i = 9;
};

class Derive :public Base {
public:
    int d_i = 9;
    void vir_f() {
        std::cout << "Derive::vir_f()" << std::endl;
    }

    void vir_g() {
        std::cout << "Derive::vir_g()" << std::endl;
    }

    void vir_h() {
        std::cout << "Derive::vir_h()" << std::endl;
    }
};

int main()
{
    Derive derive;

    return 0;
}

通过VS2019的“快速监视”窗口,我们可以看到derive对象的内存布局是这样的:

从图中我们可以看到derive对象的内存布局顺序从上到下是这样排列的:

虚函数表指针vptr、Base类的int变量b_i、Derive类的int变量d_i。

关于虚函数,有这么几个要点:

(1)只要在父类中是虚函数,子类中不用写virtual也依然是虚函数。

(2)如果父类中有虚函数,就算子类没有虚函数,子类也会创建自己的虚函数表。

(3)子类中的虚函数会覆盖父类中的同名虚函数。

(4)在把子类对象赋值给父类时,会用子类对象的值去初始化父类对象,但父类对象的虚函数表指针指向的依然是它自己的虚函数表。

关于第4点,我们可以通过下面的代码来验证:

Derive derive;
Base base = derive;
long* pvptr = (long*)&base; //因为对象的首地址是虚函数表指针
long* vptr = (long*)(*pvptr); //指针的值就是指向的虚函数表

//定义函数指针,指向 void (void)函数
typedef void (*Func)(void);

//因为虚函数表是个数组,所以我们可以通过vptr[i]的方式来调用对应的虚函数
Func vir_f = (Func)vptr[0];
Func vir_g = (Func)vptr[1];
Func vir_h = (Func)vptr[2];

vir_f();
vir_g();
vir_h();

输出结果如下:

Base::vir_f()

Base::vir_g()

Base::vir_h()

3.2 虚函数的动态绑定

前面说过:在把子类对象赋值给父类时,会用子类对象的值去初始化父类对象,但父类对象的虚函数表指针指向的依然是它自己的虚函数表。

Derive derive;
Base base = derive;
base.vir_f(); //输出Base::vir_f()

这里读者是否有疑问,这个base是用derive对象赋值的,应该调用Derive类的 vir_f()方法才对啊?

这里,必须说一下“静态绑定”、“动态绑定”这2个概念。

(1)静态绑定:绑定的是静态类型,所对应的函数或者属性依赖对象的静态类型,发生在编译期。

(2)动态绑定:绑定的是动态类型,所对应的函数或者属性依赖对象的动态类型,发生在运行期。

虚函数是动态绑定的,Base base = derive;只是把derive对象是值赋给了base对象,base对象已经确定了,没有动态绑定的动作,所以base.vir_f()只能调用自己的函数。要调用子类的虚函数,必须用类指针去动态调用。

Derive derive;
Base* pb = &derive; //输出Derive::vir_f()
pb->vir_f();

Base& b = derive; //输出Derive::vir_f()
b.vir_f();

3.3 多重继承

我们在前面例子的基础上,加入一个新类Base2:

class Base2 {
public:
    int b2_i = 10;
    virtual void vir_f2() {
        std::cout << "Base2::vir_f2()" << std::endl;
    }

    virtual void vir_g2() {
        std::cout << "Base2::vir_g2()" << std::endl;
    }

    virtual void vir_h2() {
        std::cout << "Base2::vir_h2()" << std::endl;
    }
};

然后让Derive类继承这个Base2类:

class Derive :public Base, public Base2 {......}

derive对象的内存布局如下:

对于多重继承下的虚函数,有几个要点:

(1)一个类如果继承自多个父类,且父类都有虚函数,则子类就会有多个虚函数表。

(2)类有几个虚函数表,其对象就会有对应的几个虚函数表指针。

(3)子类对象与第一个基类共用1个虚函数表指针。

C++对象模型详解(整理) 一、何为C++对象模型? C++对象模型可以概括为以下2部分: 1. 语言中直接支持面向对象程序设计的部分 2. 对于各种支持的底层实现机制 语言中直接支持面向对象程序设计的部分,如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等等第一部分这里我简单过一下,重点在底层实现机制。 阅读详情

相关推荐

C++对象模型详解 虚函数详解(一)

本文主要介绍了类对象的基本知识及其内存分布。虚函数在不同集成的情况下内存分布 虚函数指针分布

zhaopenglihui的博客 1098

C++中子类对象赋值给父类

在C++中,将子类对象赋值给父类有几种常见方式,包括直接赋值、通过指针或引用进行赋值,以及通过构造函数赋值。在C++中,将子类对象赋值给父类对象时,会发生。

weixin_50615869的博客 671

华为昇腾910B3显卡实战:手把手教你部署DeepSeek 32B模型(含Bge-zh向量库配置)

本文详细介绍了在配备8张华为昇腾910B3显卡的服务器上,从零部署DeepSeek 32B大模型推理服务的完整实战方案。内容涵盖环境准备、模型配置、多卡负载分配,并集成了BGE中文向量模型与ChromaDB向量数据库,最终通过Open WebUI构建了可投入生产的企业级AI应用栈,为私有化大模型部署提供了具体参考。

weixin_29038345的博客 1710

c++多态之子类对象赋值给父类对象

直接将子类对象赋值给父类对象可以在父类对象中保存子类对象内的基类部分,但无法将父类对象的虚函数表改变,无法实现多态。

hldgs的博客 969

《iOS之一起进大厂》系列-秒杀面试官的RunLoop底层原理面试题

Runloop是iOS面试中的必考题,不管大厂小厂都会问到。 这里包括了Runloop几乎所有的面试题,还有中肯完整的答案解释。

空杯子 1153

Spring Security 珍藏版快速入门

文章目录一、前期配置二、Spring Security 测试三、案例:自定义页面(XML配置用户信息)三、案例:自定义页面(数据库存储用户信息) 一、前期配置 (1)pom.xml <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${s

南淮北安的博客 633

C++对象模型之虚函数

C++之虚函数 前言 本次笔记记录如下知识点 虚函数表指针位置分析 继承关系下虚函数的调用 虚函数表的分析 虚函数表指针位置分析 一个类中如果有虚函数,针对这个类会产生一个虚函数表,生成这个类对象的时候,该对象就有一个指针用来指向这个虚函数表的起始位置。 虚函数表指针位于对象内存的起始位置。 如下示例: #include <iostream> using namespace std; class A{ public: int i; virtual void fun(

qq_40223389的博客 372

15、降维算法:原理、实现与应用

本文深入介绍了多种降维算法,包括线性方法如主成分分析(PCA)和线性判别分析(LDA),以及非线性方法如Sammon映射、t分布随机邻域嵌入(t-SNE)和自编码器。文章通过C++库Dlib和Shogun展示了这些算法的具体实现,并结合图像压缩和数据可视化等实际案例,分析了不同算法的适用场景。此外,还探讨了降维算法的选择策略、优化改进方法以及未来发展趋势,为读者提供了全面的技术理解和应用指导。

lemon的博客 116

深度探索C++对象模型之美

从概念来上来讲,每一个没有定义构造函数的类都会由编译器来合成一个默认构造函数,以使得可以定义一个该类的对象,但是默认构造函数是否真的会被合成,将视是否有需要而定。C++ standard 将合成的默认构造函数分为 trivial(不重要) 和 notrivial(重要) 两种,前文所述的四种情况对应于notrivial默认构造函数,其它情况都属于trivial。对于一个trivial默认构造函数,编译器的态度是,既然它全无用处,干脆就不合成它。

谢谢大家的关注 3494

UG\NX二次开发 分割曲线

分割曲线没有ufun函数?那就换一种思路实现。

王牌飞行员_里海的博客 1147

Parasoft C++Test软件集成测试(部件测试)_实例讲解

本文以一组功能函数组件作为示例,讲解了如何使用C++Test进行软件集成测试。本文的示例比较简单,但是思路、方法和流程都是通用的,希望对大家有所帮助。

文章主要涉及嵌入式软件开发、测试、项目管理等,欢迎交流,共同进步。 734

详解 C++ 对象模型

文章目录何为 C++ 对象模型?基本 C++ 对象模型C++对象模型中加入单继承1. 无重写的单继承2. 有重写的单继承C++ 对象模型中加入多继承C++ 对象模型中加入虚继承1. 简单虚继承(无重复继承情况)2. 菱形继承(含重复继承、多继承情况)如何访问成员?多态如何实现? 何为 C++ 对象模型? C++ 对象模型可以概括为以下两个部分 语言中直接支持面向对象程序设计的部分 对于各种支持的底层实现机制 语言中直接支持面向程序设计的部分,如构造函数、析构函数、虚函数、继承(单继承,多继承,虚继承

qq_41033011的博客 3406

【YOLOv8科研级优化】集成YOLOv10的SCDown采样模块,让你的模型下采样更高效更精准

打开确保模型解析器能识别SCDown模块。通过将YOLOv10的SCDown模块集成到YOLOv8中,我们实现了一个“细节保留+高效下采样”的目标检测模型。这个改进不仅在工程上能提升小目标、细粒度场景的检测效果,更在科研上提供了“分阶段下采样”的创新思路——这种思路可以延伸到其他卷积网络(如ResNet、MobileNet)的下采样层改进中,帮你在论文中打造独特的研究点。

m0_71589036的博客 137

深度探索C++对象模型

深度探索C++对象模型参考链接:第1章 关于对象C++的额外成本三种对象模型简单对象模型表格驱动对象模型C++对象模型class和struct关键字的差异三种编程典范一个类的对象的内存大小一些结论[^3] 参考链接: 深度探索C++对象模型 笔记汇总 第1章 关于对象 C++的额外成本 C++较之C的最大区别,无疑在于面向对象。类相较于C的struct不仅只包含了数据,同时还包括了对于数...

zzxiaozhao的博客 3752

claude code desktop中出现Doesn‘t look like an Anthropic model - expected a gateway model route 解决方案

本文主要介绍了claude code desktop中出现Doesn’t look like an Anthropic model - expected a gateway model route referencing an Anthropic model和Server is busy. Retrying的解决方案,希望能对使用claude code desktop的同学们有所帮助。 文章目录 1. 问题描述 2. 解决方案

weixin_43178406的博客 9万+

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

C++对象模型(整理)

C++对象模型 1、何为C++对象模型? C++对象模型可以概括为以下2部分: ① 语言中直接支持面向对象程序设计的部分 面向对象程序设计部分:如构造函数、析构函数、虚函数、继承(单继承、多继承、虚继承)、多态等。 ② 对于各种支持的底层实现机制 在C++类中有两种数据成员、三种成员函数: 两种数据成员:static、nonstatic。 三种成员函数:static、nonstatic、virtual。 代码举例说明: //base.h #pragma once #include<i

Aced96的博客 3928

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

9. C++对象模型图解

上面的类声明中,三个数据成员直接内含在每一个Point3d对象中,而成员函数虽然在类中声明,却不出现在类对象(object)之中,这些函数(non-inline)属于类而不属于类对象,只会为类产生唯一的函数实例。多态,简单来说,是指在继承层次中,父类的指针可以具有多种形态—当它指向某个子类对象时,通过它能够调用到子类的函数,而非父类的函数。总结一下:不考虑虚函数与虚继承,当数据都在同一个访问标识符下,C++的类与C语言的结构体在对象大小和内存布局上是一致的,C++的封装并没有带来空间时间上的影响。

谢谢大家的关注 993

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

图解 C++ 对象模型,看这一篇就够了!

《深度探索C++对象模型》这本书对对象模型的描述如下:语言中直接支持面向对象程序设计的部分,包括了构造函数、析构函数、多态、虚函数等等。对象模型的底层实现机制并未标准化,不同编译器有一定的自由来设计对象模型的实现细节。在我看来,对象模型研究的是对象在存储上的空间和时间上的更优,并对C++面向对象技术加以支持,如以虚指针、虚表机制支持多态特性。C++中虚函数的作用主要是为了实现多态机制。多态,简单来说,是指在继承层次中,父类的指针可以具有多种形态—当它指向某个子类对象时,通过它能够调用到子类的函数,而非父类的

luoyayun361的专栏 2628

C++对象模型

Derived继承Base,只是扩充基类的虚函数表,不会新增虚函数表指针,所以size也是4;Derived_Virtual虚继承Base,根据前面的模型知道,派生类有自己的虚函数表及指针,并且有分隔符(0x00000000),然后才是虚基类的虚函数表等信息,故大小为4+4+4=12。另外,虚函数表地址的前面设置了一个指向type_info的指针,RTTI(Run Time Type Identification)运行时类型识别是有编译器在编译器生成的特殊类型信息,包括对象继承关系,对象本身的描述,

A19720252059的博客 917

深度解读《深度探索C++对象模型》之C++虚函数实现分析与效率评测(三)

本系列深入分析编译器对于C++虚函数的底层实现,最后分析C++在多态的情况下的性能是否有受影响,多态究竟有多大的性能损失。

iShare_Carlos的博客 1228

C++ 虚函数的对象模型

1.无继承的普通类:    在有虚函数的情况下类会为其增加一个隐藏的成员,虚函数表指针,指向一个虚函数表,虚函数表里面就是类的各个虚函数的地址了。那么,虚函数表指针是以什么模型加入到类里面的,虚函数表里面又是怎么安排的呢。简单来看下就可以知道了。 #include"stdafx.h" #pragma pack(8) class A{ public: int a

chengonghao的博客 2113

《深入探索C++对象模型》之一

前言:        由于经常使用C++,但是又对C++内部机制不是很深入了解,所以看了一些关于C++的经典书籍,其中有《C++ Primer》、《STL源码剖析》、《Efficitive C++》、《More Efficitive C++》。《C++ Primer》特别适合刚使用C++的程序员学习,里面详细介绍了一些C++基础知识,包括流和STL容器、适配器、算法还有C++的面向对象的一些知识。...

杨林峰的笔记 8912

C++对象模型详解 虚函数详解(二)

虚基类的定义涉及到以下几个方面:1.虚继承的语法:在继承基类时使用 virtual 关键字。2.构造函数的调用顺序:虚基类的构造函数会在最开始被调用。3.内存布局:虚基类的对象在派生类中只占用一次内存空间。2.4.2虚基表的内存布局深色版本FinalDerived 对象:Vbptr:指向虚基表的指针。derived1Value:Derived1 的成员变量。

zhaopenglihui的博客 1096

c++ 对象模型

title: object-mode-layout date: 2022/09/12 tags: c++对象模型 categories: c++对象模型

勤天下而拥家 1357

深入C++对象模型&虚函数表

多态的实现机制:        C++中虚函数的主要作用就是用来实现多态,就是使用基类的指针或者引用调用重写的虚函数,当父类的指针或引用指向父类对象时调用的是父类虚函数,当指向子类对象时调用的是子类的虚函数。那么这又是怎么实现的呢???        这都是通过虚函数表实现的,虚函数表是通过一块连续内存来存储虚函数的地址。这张表解决了虚函数重写(地址进行覆盖)的问题 。在有虚函数的对象实例中都

Sunnylunch-blog 1836
上一篇: C++指针解读(10)-- 动态内存分配
下一篇: C++对象模型(10)-- 虚函数2
老蔡编程
博客等级 码龄23年 48粉丝 75原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值