【C++ Primer | 15】C++虚函数表剖析②
多重继承
下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。
注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:
们的类继承的源代码如下所示:父类的成员初始为10,20,30,子类的为100
- #include<iostream>
- using namespace std;
- class Base1 {
- public:
- int ibase1;
- Base1() :ibase1() {}
- virtual void f() { cout << "Base1::f()" << endl; }
- virtual void g() { cout << "Base1::g()" << endl; }
- virtual void h() { cout << "Base1::h()" << endl; }
- };
- class Base2 {
- public:
- int ibase2;
- Base2() :ibase2() {}
- virtual void f() { cout << "Base2::f()" << endl; }
- virtual void g() { cout << "Base2::g()" << endl; }
- virtual void h() { cout << "Base2::h()" << endl; }
- };
- class Base3 {
- public:
- int ibase3;
- Base3() :ibase3() {}
- virtual void f() { cout << "Base3::f()" << endl; }
- virtual void g() { cout << "Base3::g()" << endl; }
- virtual void h() { cout << "Base3::h()" << endl; }
- };
- class Derive : public Base1, public Base2, public Base3 {
- public:
- int iderive;
- Derive() :iderive() {}
- virtual void f() { cout << "Derive::f()" << endl; }
- virtual void g1() { cout << "Derive::g1()" << endl; }
- };
- int main()
- {
- typedef void(*Fun)(void);
- Derive d;
- int** pVtab = (int**)&d;
- cout << "[0] Base1::_vptr->" << endl;
- Fun pFun = (Fun)pVtab[][];
- cout << " [0] ";
- pFun();
- pFun = (Fun)pVtab[][];
- cout << " [1] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [2] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [3] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [4] "; cout << pFun << endl;
- cout << "[1] Base1.ibase1 = " << (int)pVtab[] << endl;
- int s = sizeof(Base1) / ;
- cout << "[" << s << "] Base2::_vptr->" << endl;
- pFun = (Fun)pVtab[s][];
- cout << " [0] "; pFun();
- pFun = (Fun)pVtab[s][];
- cout << " [1] "; pFun();
- pFun = (Fun)pVtab[s][];
- cout << " [2] "; pFun();
- pFun = (Fun)pVtab[s][];
- cout << " [3] ";
- cout << pFun << endl;
- cout << "[" << s + << "] Base2.ibase2 = " << (int)pVtab[s + ] << endl;
- s = s + sizeof(Base2) / ;
- cout << "[" << s << "] Base3::_vptr->" << endl;
- pFun = (Fun)pVtab[s][];
- cout << " [0] "; pFun();
- pFun = (Fun)pVtab[s][];
- cout << " [1] "; pFun();
- pFun = (Fun)pVtab[s][];
- cout << " [2] "; pFun();
- pFun = (Fun)pVtab[s][];
- cout << " [3] ";
- cout << pFun << endl;
- s++;
- cout << "[" << s << "] Base3.ibase3 = " << (int)pVtab[s] << endl;
- s++;
- cout << "[" << s << "] Derive.iderive = " << (int)pVtab[s] << endl;
- }
输出结果:
使用图片表示是下面这个样子:
我们可以看到:
- 每个父类都有自己的虚表。
- 子类的成员函数被放到了第一个父类的表中。
- 内存布局中,其父类布局依次按声明顺序排列。
- 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
重复继承
面我们再来看看,发生重复继承的情况。所谓重复继承,也就是某个基类被间接地重复继承了多次。
下图是一个继承图,我们重载了父类的f()函数。
其类继承的源代码如下所示。其中,每个类都有两个变量,一个是整形(4字节),一个是字符(1字节),而且还有自己的虚函数,自己overwrite父类的虚函数。如子类D中,f()覆盖了超类的函数, f1() 和f2() 覆盖了其父类的虚函数,Df()为自己的虚函数。
- #include<iostream>
- using namespace std;
- class B
- {
- public:
- int ib;
- char cb;
- public:
- B() :ib(), cb('B') {}
- virtual void f() { cout << "B::f()" << endl; }
- virtual void Bf() { cout << "B::Bf()" << endl; }
- };
- class B1 : public B
- {
- public:
- int ib1;
- char cb1;
- public:
- B1() :ib1(), cb1('') {}
- virtual void f() { cout << "B1::f()" << endl; }
- virtual void f1() { cout << "B1::f1()" << endl; }
- virtual void Bf1() { cout << "B1::Bf1()" << endl; }
- };
- class B2 : public B
- {
- public:
- int ib2;
- char cb2;
- public:
- B2() :ib2(), cb2('') {}
- virtual void f() { cout << "B2::f()" << endl; }
- virtual void f2() { cout << "B2::f2()" << endl; }
- virtual void Bf2() { cout << "B2::Bf2()" << endl; }
- };
- class D : public B1, public B2
- {
- public:
- int id;
- char cd;
- public:
- D() :id(), cd('D') {}
- virtual void f() { cout << "D::f()" << endl; }
- virtual void f1() { cout << "D::f1()" << endl; }
- virtual void f2() { cout << "D::f2()" << endl; }
- virtual void Df() { cout << "D::Df()" << endl; }
- };
- int main()
- {
- typedef void(*Fun)(void);
- int** pVtab = NULL;
- Fun pFun = NULL;
- D d;
- pVtab = (int**)&d;
- cout << "[0] D::B1::_vptr->" << endl;
- pFun = (Fun)pVtab[][];
- cout << " [0] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [1] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [2] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [3] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [4] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [5] 0x" << pFun << endl;
- cout << "[1] B::ib = " << (int)pVtab[] << endl;
- cout << "[2] B::cb = " << (char)pVtab[] << endl;
- cout << "[3] B1::ib1 = " << (int)pVtab[] << endl;
- cout << "[4] B1::cb1 = " << (char)pVtab[] << endl;
- cout << "[5] D::B2::_vptr->" << endl;
- pFun = (Fun)pVtab[][];
- cout << " [0] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [1] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [2] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [3] "; pFun();
- pFun = (Fun)pVtab[][];
- cout << " [4] 0x" << pFun << endl;
- cout << "[6] B::ib = " << (int)pVtab[] << endl;
- cout << "[7] B::cb = " << (char)pVtab[] << endl;
- cout << "[8] B2::ib2 = " << (int)pVtab[] << endl;
- cout << "[9] B2::cb2 = " << (char)pVtab[] << endl;
- cout << "[10] D::id = " << (int)pVtab[] << endl;
- cout << "[11] D::cd = " << (char)pVtab[] << endl;
- }
输出结果:
下面是对于子类实例中的虚函数表的图:(第一份图为原作者的图,第二幅图为修改的图)
我们可以看见,最顶端的父类B其成员变量存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的。所以,如果我们使用以下语句,则会产生二义性编译错误:
- D d;
- d.ib = 0; //二义性错误
- d.B1::ib = 1; //正确
- d.B2::ib = 2; //正确
注意,上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误,但B类在D中还是有两个实例,这种继承造成了数据的重复,我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以,C++引入了虚基类的概念。
钻石型多重虚继承
1. 虚继承
- class B
- {
- public:
- int _b;
- };
- class C1 :virtual public B
- {
- public:
- int _c1;
- };
- class C2 :virtual public B
- {
- public:
- int _c2;
- };
- class D :public C1, public C2
- {
- public:
- int _d;
- };
- int main()
- {
- D d;
- d._d = ;
- return ;
- }
内存布局:先是C1类中的成员,再是C2类中的成员,最后是D类自己的成员,如下图:
2. 虚函数
虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的。钻石型的结构是其最经典的结构。也是我们在这里要讨论的结构:
上述的“重复继承”只需要把B1和B2继承B的语法中加上virtual 关键,就成了虚拟继承,其继承图如下所示:
上图和前面的“重复继承”中的类的内部数据和接口都是完全一样的,只是我们采用了虚拟继承,其省略后的源码如下所示:
- 1 class B {……};
- 2 class B1 : virtual public B{……};
- 3 class B2: virtual public B{……};
- 4 class D : public B1, public B2{ …… };
在看菱形虚拟继承之前,我们先看一下简单的虚拟单继承是怎么样的,这样便于我们理解复杂一点的菱形虚拟继承,我们先看一组代码:
- class A {
- public:
- int _a;
- virtual void fun1() {}
- };
- class B : public virtual A {
- public:
- int _b;
- //virtual void fun1() {}
- //virtual void fun2() {}
- };
- int main()
- {
- B b;
- b._a = ;
- b._b = ;
- cout << sizeof(A) << endl;
- cout << sizeof(B) << endl;
- getchar();
- return ;
- }
在VS2013的测试结果为8和16,我们试着去掉 //virtual void fun1() {}的注释,也就是
- class B : public virtual A {
- public:
- int _b;
- virtual void fun1() {}
- //virtual void fun2() {}
- };
此时测试结果仍为8和16,但是当我们去掉//virtual void fun2() {}的注释,也就是
- class B : public virtual A {
- public:
- int _b;
- virtual void fun1() {}
- virtual void fun2() {}
- };
测试结果为sizeof(A) = 8,sizeof(B) = 20。这是为什么?为了解决这个问题,我们有必要看看在这几种情况下的B对象模型,A类对象模型比较简单,我们知道虚函数必有一个指向虚表的指针,再加上A类对象本身有个int型数据加起来就是8。而对于B对象模型,我们可以简单分几种情况:
子类有覆盖(重写)且没有新增虚函数 and 子类没有覆盖(重写)且没有新增虚函数:这两种情况并没有太大差别,对于B对象模型都是下面这种:
唯一的区别就是基类A的虚表指针指向的虚表有没有被重写而已,因此在第一种和第二种情况下,sizeof(B) = 16。
而对于有新增虚函数这种情况,对于B的对象模型则是这样的:
因为有重写基类的虚函数了,所以子类需要额外加一个虚表指针,这样sizeof(B) =20就不难理解了。有了这些知识,我们再看菱形虚拟继承就容易多了,首先对于菱形虚拟继承,它的继承层次图大概像下面这个样子:
为了便于分析,我们可以把这个图拆解下来,也就是说从B到B1,B2是两个单一的虚拟继承,而从B1,B2到则是多继承,这样一来,问题就变得简单多了。对于B到B1,B2两个单一的虚拟继承,根据前面讲的很容易得到B1,B2的对象模型:
接下来就是多继承,这样终于得到了我们D d的对象模型了:
3. 最后再看几道有关的虚继承的题目
对这四种情况分别求sizeof(a), sizeof(b)。结果是什么样的呢?我在VS2013的win32平台测试结果为:
第一种:4,12
第二种:4,4
第三种:8,16
第四种:8,8
参考资料
【C++ Primer | 15】C++虚函数表剖析②的更多相关文章
- 【C++ Primer | 15】C++虚函数表剖析①
概述 为了实现C++的多态,C++使用了一种动态绑定的技术.这个技术的核心是虚函数表(下文简称虚表).本文介绍虚函数表是如何实现动态绑定的. C++多态实现的原理: • 当类中声明虚函数时,编译器会 ...
- C++虚函数表剖析
关键词:虚函数.虚表,虚表指针,动态绑定,多态 一.概述 为了实现C++的多态,C++使用了一种动态绑定的技术. 这个技术的核心是虚函数表(下文简称虚表).本文介绍虚函数表是怎样实现动态绑定的. 二. ...
- 深入剖析C++多态、VPTR指针、虚函数表
在讲多态之前,我们先来说说关于多态的一个基石------类型兼容性原则. 一.背景知识 1.类型兼容性原则 类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代.通过公有继承,派 ...
- 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数
一.多态 多态性是面向对象程序设计的重要特征之一. 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态的实现: 函数重载 运算符重载 模板 虚函数 (1).静态绑定与动态绑 ...
- C++ 虚函数表解析
转载:陈皓 http://blog.csdn.net/haoel 前言 C++中 的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实 ...
- C++虚函数与虚函数表
多态性可分为两类:静态多态和动态多态.函数重载和运算符重载实现的多态属于静态多态,动态多态性是通过虚函数实现的. 每个含有虚函数的类有一张虚函数表(vtbl),表中每一项是一个虚函数的地址, 也就是说 ...
- C++的虚函数表
这里的例子全部来自陈皓的C++ 虚函数表解析,经过修改的. 编译器:g++ (Ubuntu 4.9.2-10ubuntu13) 4.9.2 环境:ubuntu 15.04 64位系统(地址占8字节) ...
- C++ 虚函数表解析(转载)
转载自:陈皓 http://blog.csdn.net/haoel/article/details/1948051/ 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型 ...
- 转载:C++ 虚函数表解析
目录(?)[+] 转载:http://blog.csdn.net/haoel/article/details/1948051# 前言 C++中 的虚函数的作用主要是实现了多态的机制.关于多态,简而 ...
随机推荐
- vuex2.0源码分析
当我们用vue在开发的过程中,经常会遇到以下问题 多个vue组件共享状态 Vue组件间的通讯 在项目不复杂的时候,我们会利用全局事件bus的方式解决,但随着复杂度的提升,用这种方式将会使得代码难以维护 ...
- 【官方文档】Nginx模块Nginx-Rtmp-Module学习笔记(二)HLS 指令详解
源码地址:https://github.com/Tinywan/PHP_Experience 一.在Nginx配置文件的RTMP模块中配置hls hls_key_path /tmp/hlskeys; ...
- AWT和Swing的关系
1.AWT和Swing都是java中的包. 2.AWT(Abstract Window Toolkit):抽象窗口工具包,早期编写图形界面应用程序的包,AWT是通过调用操作系统的native方法实现的 ...
- Spring面向切面编程AOP(around)实战
spring aop的环绕通知around功能强大,我们这里就不细说,直接上代码,看着注释就能明白 需要的可以点击下载源码 1.如果使用注解的方式则需要先创建个注解类 package com.mb.a ...
- 第一节:从程序集的角度分析System.Web.Caching.Cache ,并完成基本封装。
一. 揭开迷雾 1. 程序集准备 a. 需要给项目添加 System.Web 程序集. b. 需要给使用的地方添加两个引用. 2. 程序集探究 在对应的类中输入关键字 Cache,选中点 ...
- 在html中控制自动换行
其实只要在表格控制中添加一句<td style="word-break:break-all">就搞定了.其中可能对英文换行可能会分开一个单词问题:解决如下:语法: ...
- pyqt5的使用目录
pyqt5的安装 我的第一个例子 标签控件QLabel的使用 按钮QPushButton 信号与槽 pyqt5模块介绍 pycharm活动模板 QObject 定时器 QWidget类-坐标系 ...
- 上传程序Dictionary 字典 哈希--多读一写锁ReaderWriterLock
//上传程序Dictionary 字典 哈希 /// <summary> /// 车辆控制信息哈斯表,Key是终端号,Value是车辆信息控制对象 /// </summary> ...
- 重新看halcon模板匹配
工业中模板匹配有很多需求. 代码如下: read_image (Image, 'J:/测试图片/test1/1.bmp') get_image_size (Image, Width, Height) ...
- Linux驱动技术(三) _DMA编程【转】
转自:https://www.cnblogs.com/xiaojiang1025/archive/2017/02/11/6389194.html DMA即Direct Memory Access,是一 ...