C/C++杂记 虚函数的实现的基本原理(图文)
1. 概述
简单地说,每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针。例:
其中:
B的虚函数表中存放着B::foo和B::bar两个函数指针。
D的虚函数表中存放的既有继承自B的虚函数B::foo,又有重写(override)了基类虚函数B::bar的D::bar,还有新增的虚函数D::quz。
提示:为了描述方便,本文在探讨对象内存布局时,将忽略内存对齐对布局的影响。
2. 虚函数表构造过程
从编译器的角度来说,B的虚函数表很好构造,D的虚函数表构造过程相对复杂。下面给出了构造D的虚函数表的一种方式(仅供参考):
提示:该过程是由编译器完成的,因此也可以说:虚函数替换过程发生在编译时。
3. 虚函数调用过程
以下面的程序为例:
编译器只知道pb是B*类型的指针,并不知道它指向的具体对象类型 :pb可能指向的是B的对象,也可能指向的是D的对象。
但对于“pb->bar()”,编译时能够确定的是:此处operator->的另一个参数是B::bar(因为pb是B*类型的,编译器认为bar是B::bar),而B::bar和D::bar在各自虚函数表中的偏移位置是相等的。
无论pb指向哪种类型的对象,只要能够确定被调函数在虚函数中的偏移值,待运行时,能够确定具体类型,并能找到相应vptr了,就能找出真正应该调用的函数。
提示:本人曾在“C/C++杂记:深入理解数据成员指针、函数成员指针”一文中提到:虚函数指针中的ptr部分为虚函数表中的偏移值(以字节为单位)加1。
B::bar是一个虚函数指针, 它的ptr部分内容为9,它在B的虚函数表中的偏移值为8(8+1=9)。
当程序执行到“pb->bar()”时,已经能够判断pb指向的具体类型了:
如果pb指向B的对象,可以获取到B对象的vptr,加上偏移值8((char*)vptr + 8),可以找到B::bar。
如果pb指向D的对象,可以获取到D对象的vptr,加上偏移值8((char*)vptr + 8) ,可以找到D::bar。
如果pb指向其它类型对象...同理...
4. 多重继承
当一个类继承多个类,且多个基类都有虚函数时,子类对象中将包含多个虚函数表的指针(即多个vptr),例:
其中:D自身的虚函数与B基类共用了同一个虚函数表,因此也称B为D的主基类(primary base class)。
虚函数替换过程与前面描述类似,只是多了一个虚函数表,多了一次拷贝和替换的过程。
虚函数的调用过程,与前面描述基本类似,区别在于基类指针指向的位置可能不是派生类对象的起始位置,以如下面的程序为例:
5. 菱形继承
本文不讨论菱形继承的情形,个人觉得:菱形继承的复杂度远大于它的使用价值,这也是C++让人又爱又恨的原因之一。
如果想要深入研究,可以参考:Itanium C++ ABI。
相关推荐
-
浅谈C++对象的内存分布和虚函数表
c++中一个类中无非有四种成员:静态数据成员和非静态数据成员,静态函数和非静态函数. 1.非静态数据成员被放在每一个对象体内作为对象专有的数据成员. 2.静态数据成员被提取出来放在程序的静态数据区内,为该类所有对象共享,因此只存在一份. 3.静态和非静态成员函数最终都被提取出来放在程序的代码段中并为该类所有对象共享,因此每一个成员函数也只能存在一份代码实体.在c++中类的成员函数都是保存在静态存储区中的 ,那静态函数也是保存在静态存储区中的,他们都是在类中保存同一个惫份. 因此,构成对象本身的只
-
C++之普通成员函数、虚函数以及纯虚函数的区别与用法要点
普通成员函数是静态编译的,没有运行时多态,只会根据指针或引用的"字面值"类对象,调用自己的普通函数:虚函数为了重载和多态的需要,在基类中定义的,即便定义为空:纯虚函数是在基类中声明的虚函数,它可以再基类中有定义,且派生类必须定义自己的实现方法. 假设我们有三个类Person.Teacher.Student它们之间的关系如下: 类的关系图 普通成员函数 [Demo1] 根据这个类图,我们有下面的代码实现 #ifndef __OBJEDT_H__ #define __OBJEDT_H__
-
C++ 虚函数的详解及简单实例
C++ 虚函数的详解 虚函数的使用和纯虚函数的使用. 虚函数是在基类定义,然后子类重写这个函数后,基类的指针指向子类的对象,可以调用这个函数,这个函数同时保留这子类重写的功能. 纯虚函数是可以不用在基类定义,只需要声明就可以了,然后因为是纯虚函数,是不能产生基类的对象,但是可以产生基类的指针. 纯虚函数和虚函数最主要的区别在于,纯虚函数所在的基类是不能产生对象的,而虚函数的基类是可以产生对象的. // pointers to base class #include <iostream> usi
-
探讨C++中不能声明为虚函数的有哪些函数
常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数. 多态的运行期行为体现在虚函数上,虚函数通过继承方式来体现出多态作用,顶层 函数不属于成员函数,是不能被继承的 2.为什么C++不支持构造函数为虚函数? 这个原因很简单,主要是从语义上考虑,所以不支持.因为构造函数本来就是为了
-
c++中虚函数的实现详解
前言 c++ 分为编译时多态和运行时多态.运行时多态依赖于虚函数,大部分人或许听说过虚函数是由虚函数表+虚函数指针实现的,但,真的是这样吗?虽然 c++ 规范有着复杂的语言细节,但底层实现机制却任由编译器厂商想象.(没准某种特殊的处理器电路结构原生支持虚函数,没准这个处理器压根不是冯纽曼型,或者将来厂商发明了比虚函数表更有效率的数据结构.) 虚函数表 封装把实例的数据和操作结合在了一起,但实例本身只有数据,没有函数,同一个类的函数是共享的.我们通过一个例子来间接证明这一点 class Base1
-
浅谈C++基类的析构函数为虚函数
1.原因: 在实现多态时, 当用基类指针操作派生类, 在析构时候防止只析构基类而不析构派生类. 2.例子: (1). #include<iostream> using namespace std; class Base{ public: Base() {}; ~Base() {cout << "Output from the destructor of class Base!" << endl;}; void DoSomething() { cout
-
C++中的多态与虚函数的内部实现方法
1.什么是多态 多态性可以简单概括为"一个接口,多种行为". 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和模板来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.
-
C++对象内存分布详解(包括字节对齐和虚函数表)
1.C++对象的内存分布和虚函数表: C++对象的内存分布和虚函数表注意,对象中保存的是虚函数表指针,而不是虚函数表,虚函数表在编译阶段就已经生成,同类的不同对象中的虚函数指针指向同一个虚函数表,不同类对象的虚函数指针指向不同虚函数表. 2.何时进行动态绑定: (1)每个类对象在被构造时不用去关心是否有其他类从自己派生,也不需要关心自己是否从其他类派生,只要按照一个统一的流程:在自身的构造函数执行之前把自己所属类(即当前构造函数所属的类)的虚函数表的地址绑定到当前对象上(一般是保存在对象内存空间
-
浅谈C++中虚函数实现原理揭秘
编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟. 编译器对每个包含虚函数的类创建一个表(称为V TA B L E).在V TA B L E中,编译器放置特定类的虚函数地址.在每个带有虚函数的类 中,编译器秘密地置一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E.通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生
-
C/C++杂记 虚函数的实现的基本原理(图文)
1. 概述 简单地说,每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针.例: 其中: B的虚函数表中存放着B::foo和B::bar两个函数指针. D的虚函数表中存放的既有继承自B的虚函数B::foo,又有重写(override)了基类虚函数B::bar的D::bar,还有新增的虚函数D::quz. 提示:为了描述方便,本文在探讨对象内存布局时,将忽略内存对齐对布局的影响. 2. 虚函数表构造过程 从编译器的角度来说,
-
C++ 中const修饰虚函数实例详解
C++ 中const修饰虚函数实例详解 [1]程序1 #include <iostream> using namespace std; class Base { public: virtual void print() const = 0; }; class Test : public Base { public: void print(); }; void Test::print() { cout << "Test::print()" << end
-
如何获取C++类成员虚函数地址的示例代码
本文主要给大家介绍了关于如何获取C++类成员虚函数地址的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍: 1.GCC平台 GCC平台获取C++成员虚函数地址可使用如下方法[1]: class Base{ int i; public: virtual void f1(){ cout<<"Base's f1()"<<endl; } }; Base b; void (Base::*mfp)() = &Base::f1; printf(&qu
-
PHP5中虚函数的实现方法分享
请看下面的代码: 复制代码 代码如下: <?php class A { public function x() { echo "A::x() was called.\n"; } public function y() { self::x(); echo "A::y() was called.\n"; } public function z() { $this->x(); echo "A::z() was called.\n"; } }
-
C++ 类中有虚函数(虚函数表)时 内存分布详解
虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,保证其容真实反应实际的函数.这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数. 这里我们着重看一下这张虚函数表.C++的编译器应该是
-
简单解读C++中的虚函数
虚函数 简单地说,那些被virtual关键字修饰的成员函数,就是虚函数.虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离:用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略.下面来看一段简单的代码 class A{ public: void print(){ cout<<"This is A"<<endl;} }; class B:public A{ public: void print()
-
详解C++编程中的虚函数
我们知道,在同一类中是不能定义两个名字相同.参数个数和类型都相同的函数的,否则就是"重复定义".但是在类的继承层次结构中,在不同的层次中可以出现名字相同.参数个数和类型都相同而功能不同的函数. 人们提出这样的设想,能否用同一个调用形式,既能调用派生类又能调用基类的同名函数.在程序中不是通过不同的对象名去调用不同派生层次中的同名函数,而是通过指针调用它们.例如,用同一个语句"pt->display( );"可以调用不同派生层次中的display函数,只需在调用前
-
C++中继承与多态的基础虚函数类详解
前言 本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 虚函数类 继承中我们经常提到虚拟继承,现在我们来探究这种的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小看这个虚函数,他可以解决继承中许多棘手的问题,而对于多态那他更重要了,没有它就没有多态,所以这个知识点非常重要,以及后面介绍的虚函数表都极其重要,一定要认真的理解~ 现在开始概念虚函数就又引出一个概念,那就是重写(覆
随机推荐
- php 正则表达式学习笔记
- JVM类加载机制详解
- IOS 开发之 NSMutableArray与NSArray 的区别
- js实现模糊匹配功能
- 初步介绍PHP扩展开发经验分享
- Yii2实现上下联动下拉框功能的方法
- 如何在PHP中使用Oracle数据库(3)
- js以及jquery实现手风琴效果
- python 提取文件的小程序
- python在linux中输出带颜色的文字的方法
- 浅谈docker Dockerfile 指令 VOLUME 介绍
- 屏蔽PHP默认设置中的Notice警告的方法
- C#判断指定驱动器是否是Fat分区格式的方法
- jQuery模仿阿里云购买服务器选择购买时间长度的代码
- usernetschedule进程分析
- 在Tomcat中配置404自定义错误页面详解
- C语言中const,volatile,restrict的用法总结
- C++ primer基础之容器insert
- 图文详解MyEclipse更换背景主题的方法
- Python地图绘制实操详解