首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

当派生类没有覆盖虚函数时,为什么需要vptr?

当派生类没有覆盖虚函数时,仍然需要vptr的原因是为了保持多态性的特性。vptr(虚函数表指针)是一个指向虚函数表的指针,虚函数表是一个存储了虚函数地址的表格。

在C++中,当一个类声明了虚函数时,编译器会为该类生成一个虚函数表。每个对象都会有一个隐藏的指针vptr,指向该类的虚函数表。当派生类没有覆盖虚函数时,它会继承基类的虚函数,并且vptr仍然指向基类的虚函数表。这样,当通过基类指针或引用调用虚函数时,会根据vptr指向的虚函数表来确定调用的是基类的虚函数还是派生类的虚函数。

使用vptr的好处是可以实现动态绑定,即在运行时确定调用的是哪个类的虚函数,而不是在编译时确定。这样可以实现多态性,使得程序可以根据对象的实际类型来调用相应的虚函数,提高代码的灵活性和可扩展性。

腾讯云相关产品和产品介绍链接地址:

  • 腾讯云云服务器(CVM):提供弹性计算能力,满足各种业务需求。产品介绍链接
  • 腾讯云云数据库MySQL版:高性能、可扩展的关系型数据库服务。产品介绍链接
  • 腾讯云对象存储(COS):安全、稳定、高扩展性的云端存储服务。产品介绍链接
  • 腾讯云人工智能:提供丰富的人工智能服务和解决方案,如图像识别、语音识别等。产品介绍链接
  • 腾讯云物联网套件:提供全面的物联网解决方案,包括设备接入、数据管理、应用开发等。产品介绍链接
  • 腾讯云移动开发平台:提供一站式移动应用开发和运营服务,包括移动后端云服务、移动应用推送等。产品介绍链接
  • 腾讯云区块链服务:提供安全、高效的区块链解决方案,支持多种场景应用。产品介绍链接

请注意,以上链接仅为示例,具体的产品选择应根据实际需求进行评估和选择。

页面内容是否对你有帮助?
有帮助
没帮助

相关·内容

c++头脑风暴-多态、继承、多重继承内存布局

没有函数类的内存布局 一个类没有函数的时候,其实就是结构体,它的内存布局就是按照成员变量的顺序来的。...其实在普通继承(非虚继承)的时候派生类并不会重新生成表指针,只是会使用它自身的函数地址去覆盖基类的相同函数,如果是派生类独有的函数,则直接追加到函数表的最后面。...总结一下:c++继承的多态一般指的运行时多态,使用基类指针或者引用指向一个派生类对象,在非虚继承的情况下,派生类直接继承基类的表指针,然后使用派生类函数覆盖基类的函数,这样派生类对象通过表指针访问到的函数就是派生类函数了...所以这里我们又知道了一个事,之前说继承后,派生类都会生成它自己的函数表和表指针,并不完全准确,准确来讲,基类有成员变量派生类会生成它自己的函数表和表指针,派生类没有成员变量,并不会重新生成派生类自己的函数表和表指针...,如果派生类有同样的函数,那就覆盖基类表中同名函数,如果是派生类独有的函数,那就追加在基类函数表后面; 一个派生类继承于一个有函数没有成员变量的基类,则派生类也不会生成它自己的表指针和函数

68220

深入探索函数表(详细)

其实我们可以通过指针的形式去获得,因为前面也提到了,我们可以把函数表看作是一个数组,每一个单元用来存放函数的地址,那么调用的时候可以直接通过指针去调用所需要函数就行了。...,它应该是含有三个指针,分别指向基类的函数a和基类的函数c和自己的函数b(因为基类和派生类中含有同名函数,被覆盖),那么我们就用下面的方式来验证一下: Derive* p = new Derive...Base1的函数表,然后将自己的函数放在后面,对于第二个函数表中,继承了Base2的函数表,由于在Derive类中有一个函数D覆盖了Base2的函数,所以第一个表中就没有Derive::D的函数地址...每一个基类都会有自己的函数表,派生类函数表的数量根据继承的基类的数量来定。 2. 派生类函数表的顺序,和继承的顺序相同。 3....派生类自己的函数放在第一个函数表的后面,顺序也是和定义顺序相同。 4. 对于派生类如果要覆盖父类中的函数,那么会在函数表中代替其位置。

55500
  • C++多态

    多态原理 类存在函数,编译器会为该类维护一个表,这个表就是函数表(vtbl),里面存放了该类函数函数指针。在构造类的时候增加一个表指针(vptr)指向对应的函数表。...在类执行成员函数时候,先判断该函数是否是函数,如果不是函数则直接执行对应的方法,如果是函数则从函数表中找到应该调用的函数vptr的初始化是在类的构造时候完成。...4.2“覆盖”是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有virtual 关键字。...(2)如果派生类函数与基类的函数同名,但是参数相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。...小结:说白了就是如果派生类和基类的函数名和参数都相同,属于覆盖,这是可以理解的吧,完全一样当然要覆盖了;如果只是函数名相同,参数并不相同,则属于隐藏。

    44430

    C++函数表深入探索(详细全面)

    其实我们可以通过指针的形式去获得,因为前面也提到了,我们可以把函数表看作是一个数组,每一个单元用来存放函数的地址,那么调用的时候可以直接通过指针去调用所需要函数就行了。...,它应该是含有三个指针,分别指向基类的函数a和基类的函数c和自己的函数b(因为基类和派生类中含有同名函数,被覆盖),那么我们就用下面的方式来验证一下: Derive* p = new Derive...每一个基类都会有自己的函数表,派生类函数表的数量根据继承的基类的数量来定。 2. 派生类函数表的顺序,和继承的顺序相同。 3....派生类自己的函数放在第一个函数表的后面,顺序也是和定义顺序相同。 4. 对于派生类如果要覆盖父类中的函数,那么会在函数表中代替其位置。...我们发现virfunc函数的地址并不是我们设置的0,那么它就变的和普通的函数没有什么区别了(普通函数采用静态联编,在编译就绑定了函数的地址),这显然不是我们想要的函数,那么肯定就无法实现多态,对于类的多态性

    18.6K168

    深入探索函数表(详细)

    其实我们可以通过指针的形式去获得,因为前面也提到了,我们可以把函数表看作是一个数组,每一个单元用来存放函数的地址,那么调用的时候可以直接通过指针去调用所需要函数就行了。...,在第一个函数表中首先继承了Base1的函数表,然后将自己的函数放在后面,对于第二个函数表中,继承了Base2的函数表,由于在Derive类中有一个函数D覆盖了Base2的函数,所以第一个表中就没有...每一个基类都会有自己的函数表,派生类函数表的数量根据继承的基类的数量来定。 2. 派生类函数表的顺序,和继承的顺序相同。 3....派生类自己的函数放在第一个函数表的后面,顺序也是和定义顺序相同。 4. 对于派生类如果要覆盖父类中的函数,那么会在函数表中代替其位置。...0,那么它就变的和普通的函数没有什么区别了(普通函数采用静态联编,在编译就绑定了函数的地址),这显然不是我们想要的函数,那么肯定就无法实现多态,对于类的多态性,一定是基于函数表的,那么函数表的实现一定是动态联编的

    1.2K80

    函数详解

    (有函数覆盖) 4、多重继承情况(无函数覆盖) 5、多重继承情况(有函数覆盖) 四、函数的相关问题 1、构造函数为什么不能定义为函数 2、析构函数为什么要定义为函数?...可以看到,使用这三个指针调用func函数,调用的都是基类base的函数。而使用这三个指针调用函数vir_func,调用的是指针指向的实际类型的函数。...以上,我们可以得出结论使用类的指针调用成员函数,普通函数由指针类型决定,而函数由指针指向的实际类型决定。...那么,对于派生类的实例,其函数表会是下面的一个样子: 【Note】: 覆盖的f()函数被放到了表中原来父类函数的位置。 没有覆盖函数依旧在原来的位置。...那么在构造函数完成之前,vptr没有值的,也就无法通过vptr找到作为函数的构造函数所在的代码区。 2、析构函数为什么要定义为函数? 析构函数可以是函数且推荐最好设置为函数

    1.5K40

    实现多态必须满足什么条件

    为什么要用函数 A: 为什么使用派生类和基类对象之间直接赋值不能实现?? 必须用用指针或者引用?...这说明对象b1.vptr 记录函数入口地址 0x400cc8 只要a1.vptr 指向 b1. vptr 即可 1.4 a1=b1 调用 A::operator= ?...一句话解释: 1.默认的赋值运算符并不会操作函数表。 2.要实现多态,必须使用指针或者引用 为什么要用函数 如果不没有声明函数 同名函数出现覆盖现象!...函数入口地址 图片可能和代码不符 你应该可以看懂 没有函数的对象数据布局 成员类型相同: ?...有函数的对象数据布局 跟深入地方请查看《Inside the C++ Object Model》 我理解 数据部分: 对象在执行赋值 ==操作时候,如果类型不同会发生强制转换 因此需要相同成员

    76870

    剖析多态的原理及实现

    函数重写的其他问题 协变 派生类重写基类的函数,如果基类函数返回基类类型的指针或引用,派生类函数可以返回派生类类型的指针或引用。这种情况称为协变。...基类中有函数派生类可以重写该函数,从而在运行时根据实际对象的类型调用对应的函数实现。 特点: 发生在继承层次结构中。...纯函数没有具体实现,只提供接口,要求派生类必须实现该函数。通过纯函数,C++允许程序设计者定义一个抽象的接口,并要求任何继承该接口的类必须实现这些接口方法。...基类中的函数未被派生类重写派生类表会继承这些地址。 派生类函数表:派生类重写了基类的函数派生类表中的相应条目会替换为派生类函数地址。...函数表的底层工作原理 通过基类指针调用函数,程序会: 通过对象的vptr访问该对象的函数表。 在表中找到对应函数的地址。

    10910

    一文读懂C++继承的内存模型

    在图3-1中,我们是定义类D才出现了对派生的需求,但是如果类B和类C不是从类A派生得到的,那么类D还是会保留间接基类A的两份成员,示例代码如下: #include #include...因为这里的类设计比较简单,没有函数加进来,有函数的话_vptr.B或者_vptr.C下面的内存空间存储的就是指向对应函数的指针了(以下只讲_vptr.B的相关内容,_vptr.C同理就不赘述了)...这里可以看到_vptr.B指向的是函数的起始地址(因为这里没有函数,所以下面紧接着就是_vptr.C的内容),而不是与它相关联的全部信息的起始地址,事实上从图5-1中可以看出_vptr.B - 3...offset_to_top深度解析:在多继承中,由于不同基类的起点可能处于不同的位置,因此需要将它们转化为实际类型,this指针的偏移量也不相同。...本文为了更直观地展示继承的内存模型,示例设计得很简单,类的设计中只有一个成员变量而没有成员函数函数等其它内容。

    1.1K20

    再探函数

    2、纯函数是在基类中声明的函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯函数的方法是在函数原型后加"=0" 3、声明了纯函数的类是一个抽象类。...---- Q5:基类的析构函数为什么要定义成函数?...只有在基类析构函数定义为函数,调用操作符delete销毁指向对象的基类指针,才能准确调用派生类的析构函数(从该级向上按序调用函数),才能准确销毁数据。...所以在调用基类的析构函数派生类对象的数据成员已经销毁,这个时候再调用子类的函数没有任何意义。 ---- Q8:静态函数能定义为函数吗?...vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存函数地址的vtable.对于静态成员函数,它没有this指针,所以无法访问vptr

    86720

    C++进阶:详解多态(多态、函数、抽象类以及函数原理详解)

    没有报错 2.4重载、覆盖(重写)、隐藏(重定义)的对比 3.抽象类 3.1概念 在函数的后面写上= 0,则这个函数为纯函数 。...所以如果不实现多态,不要把函数定义成函数 普通函数的继承(实现继承): 在普通函数的继承中,派生类继承了基类的函数的具体实现。 派生类可以直接使用基类的函数,而不需要重新实现该函数。...函数表指针(vptr)的赋值是在对象的构造函数中完成的。具体来说,创建一个对象,首先会调用该对象的构造函数,而构造函数的初始化列表是在对象实际构造之前执行的。...基类指针或引用调用函数,编译器生成的机器代码确实会先访问对象的函数指针(vptr),再通过函数表(vtable)找到实际要调用的函数的地址,最终进行调用。...这种动态绑定的过程使得程序在运行时能够根据对象的实际类型来调用正确的函数,实现了多态性 为什么多态必须要用基类的指针 / 引用来调用函数,而用基类对象调用却不行 派生类对象赋值给基类对象

    54310

    C++基础-多态

    ,基类和派生类对象都会含有各自的 VFT 指针,即使派生类没有函数。...基类中的函数,基类中要给出实现,派生类可实现也可不实现,即派生类需要覆盖基类中的函数 基类中的普通函数,基类中要给出实现,派生类可实现也可不实现。...普通函数不支持多态,所以需要继承的函数应声明为函数,不应使用普通函数 5. 使用继承解决菱形问题 一个类继承多个父类,而这多个父类又继承一个更高层次的父类,会引发菱形问题。...C++关键字 virtual 被用于实现两个不同的概念,其含义随上下文而异,如下: 在函数声明中, virtual 意味着基类指针指向派生对象,通过它可调用派生类的相应函数。...• 派生类中被声明为 override 的函数是否是基类中对应函数覆盖?确保没有有手误写错。 编程实践:在派生类中声明要覆盖基类函数函数,务必使用关键字 override 7.

    85220

    本周阅读:深度探索C++对象模型

    译成中文就是,编译器必须要确保如果一个对象有一个或多个vptr,这些vptr不是由原对象来初始化或改变的。 也就是说:使用赋值的方式或拷贝构造的方式创建一个对象,这个对象的vptr与源对象无关。...一个类对于默认的拷贝赋值操作, 在以下情况不会表现出bitwise拷贝语意: * 类内带一个成员对象, 而其类有一个拷贝赋值操作 * 一个类的基类有一个拷贝赋值操作 * 一个类声明了任何函数...(我们一定不能拷贝右端类对象的vptr地址, 因 为它可能是一个继承类对象) * 类继承自一个基类(不论此基类有没有拷贝操作) https://github.com/wangcy6/weekly...The Semantics of the vptr Initialization 构造函数的执行算法通常如下: 在派生类构造函数中, 所有基类以及上一层基类的构造函数会被调用 上述完成之后, 对象的...vptr会被初始化, 指向相关的表 如果有成员初始化列表的话, 将在构造函数体内扩展开来; 这必须在vptr被 设定之后才进行, 以免有一个成员函数被调用 最后执行程序员所提供的代码 编程规范 不要在构造函数中调用函数

    78220

    C++ 虚拟继承

    为什么需要继承? 由于C++支持多重继承,那么在这种情况下会出现重复的基类这种情况,也就是说可能出现将一个类两次作为基类的可能性。比如像下面的情况 ?...因为每个存在函数的类都要有一个4字节的指针指向自己的函数表,所以每种情况的类a所占的字节数应该是没有什么问题 的,那么类b的字节数怎么算呢?...4.2“覆盖”是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有virtual 关键字。...(2)如果派生类函数与基类的函数同名,但是参数相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。...小结:说白了就是如果派生类和基类的函数名和参数都相同,属于覆盖,这是可以理解的吧,完全一样当然要覆盖了;如果只是函数名相同,参数并不相同,则属于隐藏。

    2.3K80

    析构函数vptr? 指针偏移?多态数组? delete 基类指针 内存泄漏?崩溃?

    2、在遇到通过基类指针或引用调用函数的语句,首先根据指针或引用的静态类型来判断所调函数是否属于该class或者它的某个public 基类,如果 属于再进行调用语句的改写: (*(p->_vptr[slotNum...]))(p, arg-list); 其中p是基类指针,vptr是p指向的对象的隐含指针,而slotNum 就是调用的函数指针在vtable 的编号,这个数组元素的索引号在编译就确定下来, 并且不会随着派生层的增加而改变...如果没有这样做的话,只会输出基类的 析构函数,这种输出情况通过比对规则2也可以理解,pI 现在虽然指向派生类对象首地址,但执行pI->~IRectangle() 发现不是函数,故直接调用, 假如在派生类析构函数内有释放内存资源的操作...由于基类的fun不是函数,故p->fun() 调用的是Base::fun()(规则2),而且delete p 还会崩溃,为什么呢?...将基类析构函数改成函数,fun() 最好也改成函数,只要有一个函数,基类大小就为一个vptr ,此时基类和派生类大小都是4个字节,p也指向派生类的首地址,问题解决,参考规则3。

    1K20

    从零开始学C++之函数与多态(一):函数表指针、析构函数、object slicing与函数、C++对象模型图

    由于vptr在对象中的偏移不会随着派生层次的增加而改变,而且改写的函数派生类vtable中的位置与它在基类vtable中的位置始终保持一致,有了这两条保证,再加上被改写函数与其基类中对应函数的原型和调用规范都保持一致...调用B的构造函数,先会调用B的基类A的构造函数。然后在A的构造函数里调用Print。由于此时实例的类型B的部分还没有构造好,本质上它只是A的一个实例,它的函数表指针指向的是类型A的函数表。...此时父类.show()并没有覆盖....四、析构函数 何时需要析构函数?...当你可能通过基类指针删除派生类对象 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),并且被析构的派生类对象是有重要的析构函数需要执行,就需要让基类的析构函数作为函数

    1.1K00

    析构函数vptr? 指针偏移?多态数组? delete 基类指针 内存泄漏?崩溃?

    2、在遇到通过基类指针或引用调用函数的语句,首先根据指针或引用的静态类型来判断所调函数是否属于该class或者它的某个public 基类,如果 属于再进行调用语句的改写: (*(p->_vptr[slotNum...]))(p, arg-list); 其中p是基类指针,vptr是p指向的对象的隐含指针,而slotNum 就是调用的函数指针在vtable 的编号,这个数组元素的索引号在编译就确定下来, 并且不会随着派生层的增加而改变...如果没有这样做的话,只会输出基类的 析构函数,这种输出情况通过比对规则2也可以理解,pI 现在虽然指向派生类对象首地址,但执行pI->~IRectangle() 发现不是函数,故直接调用, 假如在派生类析构函数内有释放内存资源的操作...由于基类的fun不是函数,故p->fun() 调用的是Base::fun()(规则2),而且delete p 还会崩溃,为什么呢?...将基类析构函数改成函数,fun() 最好也改成函数,只要有一个函数,基类大小就为一个vptr ,此时基类和派生类大小都是4个字节,p也指向派生类的首地址,问题解决,参考规则3。

    95700

    C++|对象模型|对象模型综述

    如下图所示:派生类都可以通过vptr获取offset,从而正确地指向基类的地址。...因此应当存在对象中,而且仅class真正需要才存在。...在派生类指针赋值给基类指针编译器需要调整 Base2 * pbase2 =temp ? temp + sizeof (Base1):0; 目的是防止temp==nullptr,仍然出现偏移。...而在基类指针调用派生类重写的函数,则需要反过来调整this指针(由编译器插入或者thunk引入),从而正确指向对应的表。...函数较小时,产生两个函数,根据调用的指针类别判断是否需要调用有调整的函数 函数较大,产生多重进入点,函数体分为(1)调整this (2)执行自定义函数码,根据是否需要调整,通过thunks跳转至对应的进入点

    66310

    C++重要知识点小结---2

    如果在基类中没有保留位置,则就没有重载。 纯函数是一个没有定义函数语句的基类函数,纯函数的值是0.派生类必须为每一个基类纯函数提供一个相应的函数定义。...在派生类中允许重载基类的成员函数。如果基类中的函数函数使用指针或引用访问对象,将基于实际运行时指针所指向的对象类型来调用派生类函数。...因为每个存在函数的类都要有一个4字节的指针指向自己的函数表,所以每种情况的类a所占的字节数应该是没有什么问题 的,那么类b的字节数怎么算呢?...纯函数 C++的纯函数用于表示一个类不能被创建实例, 必需是子类覆盖该方法的定义后,方可新建类实例,格式是在函数后面添加 = 0。...Range *r1 = new Circle(3, 4); 如果析构函数不是函数,则r1在释放内存,则调用提Range的析构函数

    70570

    深入浅出C++函数vptr与vtable

    深入浅出C++函数vptr与vtable 1.基础理论 为了实现函数,C ++使用一种称为虚拟表的特殊形式的后期绑定。该虚拟表是用于解决在动态/后期绑定方式的函数调用函数的查找表。...首先,每个使用函数的类(或者从使用函数的类派生)都有自己的虚拟表。该表只是编译器在编译设置的静态数组。虚拟表包含可由类的对象调用的每个函数的一个条目。...此表中的每个条目只是一个函数指针,指向该类可访问的最派生函数。 其次,编译器还会添加一个隐藏指向基类的指针,我们称之为vptrvptr在创建类实例自动设置,以便指向该类的虚拟表。...Base::fun1() 基类指针指向派生类实例并调用函数 Derived::fun1() 基类引用指向基类实例并调用函数 Derived::fun1() ====================...例如,上述通过基类指针指向派生类实例,并调用函数,将上述代码简化为: Base *pt = new Derived(); // 基类指针指向派生类实例 cout<<"基类指针指向派生类实例并调用函数

    4.2K30
    领券