【深度探索c++对象模型】Function语义学之虚函数
虚函数的一般实现模型:每一个class有一个virtual table,内含该class中的virtual function的地址,然后每个object有一个vptr,指向virtual table。
识别class是否支持多态,唯一恰当的方法是看它是否有virtual function,只要class拥有virtual function,它就需要额外的执行期信息(以下两点)。
考虑ptr->z(),ptr是基类指针,z是虚函数,为了找到并调用z()的适当实体,我们需要两项信息:
1.ptr所指对象的真实类型(ptr的动态类型,在运行时决定)
2.z()实体位置(放在virtual table中)
如何实现上述信息?
在每个多态的class object身上添加两个members:
1.一个字符串或数字,表示class的类型
2.一个指针,指向某表格,表格中带有程序的虚函数的执行期地址
在c++中,虚函数可以在编译时获知,它们地址是固定不变的,放在virtual table中。为了找到virtual table,每个class object被安插一个由编译器内部产生的指针,指向该表格。为了找到函数地址,每一个虚函数被指派一个表格索引值。以上的工作由编译器完成,程序运行时要完成的是在特定的virtual table slot(记录着虚函数地址)中激活虚函数。
(编译时把每个类的虚函数地址放到该类对应的virtual table中,运行时根据指针或引用的动态类型在对应的virtual table中找到目标函数并调用)
一个class只有一个virtual table,每个table内含其对应的class object中所有active virtual function的地址。这些active virtual function包括:
1.这个类所定义的函数实体,它会改写一个可能存在的base class virtual function函数实体(覆盖)
2.继承自base class的函数实体,这是在派生类不改写virtual function时才会出现的情况
当一个类继承自另一个类时,会发生以下三种情况:
1.它可以继承base class 所声明的virtual function的函数实体,即该函数实体的地址会被拷贝到派生类的virtual table相对应的slot中
2.它可以使用自己的函数实体,该函数实体覆盖了父类的虚函数(覆盖)
3.它可以加入一个新的虚函数,这时候virtual table的尺寸会增加一个slot,而新的函数实体地址会被放进该slot之中
----------------------------------------------------------------------------------------------------------------------------------
虚函数表详解(转载)
一般继承(无虚函数覆盖)
对于实例Derive d的虚函数表如下:
我们可以看到:
1)虚函数按照其声明顺序放入表中
2)父类的虚函数在子类的虚函数前面
一般继承(有虚函数覆盖)
对于派生类的实例,其虚函数表如下:
我们从表中可以看到下面几点,
1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2)没有被覆盖的函数依旧。
这样,我们就可以看到对于下面这样的程序,
Base *b = new Derive();
b->f();
b的动态类型是派生类指针,在派生类的虚函数表中查找Derive函数,而在该表中Derive函数为派生类函数实体地址,所以调用的是派生类函数。由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。
多重继承(无虚函数覆盖)
对于子类实例中的虚函数表,是下面这个样子:
我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的),即子类有多个虚表,分别对应不同的父类,自己自定义的虚函数放在第一个虚表中
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
多重继承(有虚函数覆盖)
下面是对于子类实例中的虚函数表的图:
我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。
class a
{
public:
virtual void f1(){}
}; class b
{
public:
virtual void f2(){}
}; class c
{
public:
virtual void f3(){}
}; class d: public a, public b, public c
{
public:
virtual void f4(){}
}; void main()
{
cout << sizeof(a) << " " << sizeof(b) << " " << sizeof(c) <<" "<<sizeof(d)<< endl;
system("pause");
}
vs2013输出为4,4,4,12
安全性
一、通过父类型的指针访问子类自己的虚函数
任何妄图使用父类指针想调用子类中的独有的的成员函数的行为都会被编译器视为非法,因为这些成员不在父类指针所指内存空间里,编译器是根据指针的静态类型来确定调用对象的地址空间的,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。
二、访问non-public的虚函数
另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们(类的用户不能访问类的private或protected成员)同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。
【深度探索c++对象模型】Function语义学之虚函数的更多相关文章
- 深度探索C++对象模型第四章:函数语义学
C++有三种类型的成员函数:static/nonstatic/virtual 一.成员的各种调用方式 C with class 只支持非静态成员函数(Nonstatic member function ...
- [读书系列] 深度探索C++对象模型 初读
2012年底-2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起<深度探索C++对象模型>这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio ...
- 深度探索C++对象模型
深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...
- 《深度探索C++对象模型》读书笔记(一)
前言 今年中下旬就要找工作了,我计划从现在就开始准备一些面试中会问到的基础知识,包括C++.操作系统.计算机网络.算法和数据结构等.C++就先从这本<深度探索C++对象模型>开始.不同于& ...
- c++学习书籍推荐《深度探索C++对象模型》下载
百度云及其他网盘下载地址:点我 百度云及其他网盘下载地址:点我 编辑推荐 如果你是一位C++程序员,渴望对于底层知识获得一个完整的了解,那么这本<深度探索C++对象模型>正适合你 作者简介 ...
- 读书笔记《深度探索c++对象模型》 概述
<深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...
- 拾遗与填坑《深度探索C++对象模型》3.3节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
- 柔性数组-读《深度探索C++对象模型》有感 (转载)
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
- 柔性数组-读《深度探索C++对象模型》有感
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
随机推荐
- class 写在 import的位置 类的名字第一个字母大写 后面没括号 ES6
class 写在 import的位置 类的名字第一个字母大写 后面没括号 class ObTableDataClass {}或者 const ObTableDataClass = class { in ...
- postman使用方法详解
postman的使用方法详解 Collections:在Postman中,Collection类似文件夹,可以把同一个项目的请求放在一个Collection里方便管理和分享,Collection里 ...
- Oracle批量更新数据,使用begin end
/* 使用begin end批量更新 注意end后面必须使用;结束 并且每条update语句都要用;来结束 所以close为;END; 是为了补全语法 */ <foreach collectio ...
- 数组合并--php
常用的合并数组方法有以下几种: 1 array_merge 2 '+' 3 array_merge_recursive 下面是一段对比的代码 $array1 = array(2,4," ...
- CentOS6.8下安装Docker
原文章链接https://www.cnblogs.com/baolong/p/5743420.html. 由于在自己安装的虚拟机上打开linux终端命令行输入uname -a 以及cat /etc/r ...
- 2018 GDCPC 省赛总结
第二次参加省赛了,对比上年连STL都不会的acm入门者来说, 今年是接触acm的第二年. 首先要说的是今年的省赛比上年人数多了很多, 闭幕式200多支队伍坐满了整个礼堂还要站着不少人,所以今年的竞争其 ...
- LeetCode 304. Range Sum Query 2D – Immutable
Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper lef ...
- Java学习之流Stream理解(一)
缓存可以说是I/O的一种性能优化.缓存流为I/O流增加了内存缓冲区.有了缓冲区,使得在流上执行skip().mark()和reset()方法都称为可能. 1.BufferedInputStream 类 ...
- Java 新手学习日记一
Java 基础知识点掌握: 数据类型 变量就是申请内存来存储值.也就是说,当创建变量的时候,需要在内存中申请空间.内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据. 因此 ...
- 安装weblogic时,运行configure.cmd报错、闪退、无法创建域
直接运行configure.cmd时在jar包加载完成时,不提示创建域的过程,而是直接退出程序 命令行: cd /d F:\00uep_rfs\wls1212_dev\wls12120 切换至解压路径 ...