1.前言与准备

工欲善其事,必先利其器,我们先用好Visual Studio工具,像下面这样一步一步来:

     

先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局。近期的VS版本都支持这样配置。

2.单继承实验

下面可以定义一个类,像下面这样:

class Base
{int a;int b;
public:void CommonFunction();
};

然后编译一下,可以看到输出框里面有这样的排布:

这里不想花精力在内存对齐因素上,所以成员变量都设为int型。从这里可以看到普通类的排布方式,成员变量依据声明的顺序进行排列(类内偏移为0开始),成员函数不占内存空间

再看下继承,往后面添加如下代码:

class DerivedClass: public Base
{int c;
public:void DerivedCommonFunction();
};

编译,然后看到如下的内存分布(父类的内存分布不变,这里只讨论子类成员变量的内存分布):

可以看到子类继承了父类的成员变量,在内存排布上,先是排布了父类的成员变量,接着排布子类的成员变量,同样,成员函数不占字节

下面给基类加上虚函数,暂时注释掉DerivedClass,看一下这时的内存排布

class Base
{int a;int b;
public:void CommonFunction();void virtual VirtualFunction();
};

这个内存结构图分成了两个部分,上面是内存分布,下面是虚表,我们逐个看。VS所带编译器是把虚表指针放在了内存的开始处(0地址偏移),然后再是成员变量;下面生成了虚表,紧跟在&Base1_meta后面的0表示,这张虚表对应的虚指针在内存中的分布,下面列出了虚函数,左侧的0是这个虚函数的序号,这里只有一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数列出来。

编译器是在构造函数创建这个虚表指针以及虚表的。

那么编译器是如何利用虚表指针与虚表来实现多态的呢?

是这样的,当创建一个含有虚函数的父类的对象时,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。

所以,如果是调用Base *p = new Derived();生成的是子类的对象,在构造时,子类对象的虚指针指向的是子类的虚表,接着由Derived*到Base*的转换并没有改变虚表指针,所以这时候p->VirtualFunction,实际上是p->vfptr->VirtualFunction,它在构造的时候就已经指向了子类的VirtualFunction,所以调用的是子类的虚函数,这就是多态了。

#########################################################

下面加上子类,并在子类中添加虚函数,像下面这样:

class DerivedClass: public Base
{int c;
public:void DerivedCommonFunction();void virtual VirtualFunction();
};

可以看到子类内存的排布如下:

上半部是内存分布,可以看到,虚表指针被继承了,且仍位于内存排布的起始处,下面是父类的成员变量a和b,最后是子类的成员变量c,注意虚表指针只有一个,子类并没有再生成虚表指针了;下半部的虚表情况与父类是一样的。

#######################################################

我们把子类换个代码,像这样:

class DerivedClass1 : public Base
{int c;
public:void DerivedCommonFunction();void virtual VirtualFunction2();
};

注意到这时我们并没有覆写父类的虚方法,而是重声明了一个新的子类虚方法,内存分布如下:

还是只有一个虚表指针,但是下方虚表的内容变化了,虚表的0号是父类的VirtualFunction,而1号放的是子类的VirtualFunction2。也就是说,如果定义了DerivedClass的对象,那么在构造时,虚表指针就会指向这个虚表,以后如果调用的是VirtualFunction,那么会从父类中寻找对应的虚函数,如果调用的是VirtualFunction2,那么会从子类中寻找对应的虚函数。

3.多继承实验

下面来讨论多重继承,代码如下:

class Base
{int a;int b;
public:void CommonFunction();void virtual VirtualFunction();
};class DerivedClass1: public Base
{int c;
public:void DerivedCommonFunction();void virtual VirtualFunction();
};class DerivedClass2 : public Base
{int d;
public:void DerivedCommonFunction();void virtual VirtualFunction();
};class DerivedDerivedClass : public DerivedClass1, public DerivedClass2
{int e;
public:void DerivedDerivedCommonFunction();void virtual VirtualFunction();
};

内存分布从父类到子类,依次如下:

  

Base中有一个虚表指针,地址偏移为0。DerivedClass1继承了Base,内存排布是先父类后子类。

  

DerivedClass2的情况是类似于DerivedClass1的。

下面我们重点看看这个类DerivedDerivedClass,由外向内看,它并列地排布着继承而来的两个父类DerivedClass1与DerivedClass2,还有自身的成员变量e。DerivedClass1包含了它的成员变量c,以及Base,Base有一个0地址偏移的虚表指针,然后是成员变量a和b;DerivedClass2的内存排布类似于DerivedClass1,注意到DerivedClass2里面竟然也有一份Base。

这里有两份虚表了,分别针对DerivedClass1与DerivedClass2,在&DerivedDericedClass_meta下方的数字是首地址偏移量,靠下面的虚表的那个-16表示指向这个虚表的虚指针的内存偏移,这正是DerivedClass2中的{vfptr}在DerivedDerivedClass的内存偏移。

4.总结

虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(更多的虚表指针)。

下面总结一下(当基类有虚函数时):

1. 每个类都有虚指针和虚表;

2. 如果不是虚继承,那么子类将父类的虚指针继承下来,并指向自身的虚表(发生在对象构造时)。有多少个虚函数,虚表里面的项就会有多少。多重继承时,可能存在多个的基类虚表与虚指针;

3. 如果是虚继承,那么子类会有两份虚指针,一份指向自己的虚表,另一份指向虚基表,多重继承时虚基表与虚基表指针有且只有一份

C++类内存分布——深度理解继承与虚函数相关推荐

  1. C++内存分布之菱形继承(无虚函数)

    菱形继承的定义是:两个子类继承同一父类,而又有子类同时继承这两个子类.例如a,b两个类同时继承c,但是又有一个d类同时继承a,b类.探究的过程还是很有趣的. 菱形继承的内存布局探究花了我几天时间,探究 ...

  2. 继承、虚继承、虚函数内存分布(MSVC下)

    前提知识: 对象的内存中只包含成员变量,存储在栈区或堆区(使用 new 创建对象): 成员函数与对象内存分离,存储在代码区. 对象的大小,可以自己分析,int 四个字节,指针也是四个字节.(在x86中 ...

  3. C++类内存分布+钻石模型的解决方法

    C++类内存分布 #include<iostream> using namespace std; class Base {private:int val;public:Base(int i ...

  4. Visual C++ 2008入门经典 第九章类的继承和虚函数

    // 第九章类的继承和虚函数 //.cpp: 主项目文件. //1 继承如何与面向对像的编程思想适应 //2 根据现有类定义新类 //3 使用protected关键字为类成员指定新的访问特性 //4 ...

  5. c++虚继承和虚函数和抽象类、接口类,聚合类

    虚继承 虚继承用于解决多继承条件下的菱形继承问题(浪费存储空间.存在二义性). 底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间, ...

  6. C++_虚继承_虚函数_纯虚函数(多继承的二义性,多态)

    基本信息 每一个类都有一个虚表,以及虚表指针; 虚表的内容是编译器决定的,虚表中用于存放虚函数的指针, 程序运行时的类型信息等; 每个多态对象都存放着一个指向当前类型的虚表的指针, 该指针在构造函数中 ...

  7. C++虚继承与虚函数

    虚继承和虚函数是完全无相关的两个概念. 虚继承是解决C++多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝.这将存在两个问题:其一,浪费存储空间:第二,存在二义性问题,通常可 ...

  8. C++编程思想:继承与虚函数以及多态

    文章目录 简介 实现虚函数多态的技术原理 对象切边 析构函数和构造函数中的虚函数 使用继承的类的析构函数应该使用虚函数修饰符 简介 继承与虚函数与多态是浑然一体的三个概念,父类中虚函数可以表现出多态特 ...

  9. C++中的继承与虚函数各种概念

    虚继承与一般继承 虚继承和一般的继承不同,一般的继承,在目前大多数的C++编译器实现的对象模型中,派生类对象会直接包含基类对象的字段.而虚继承的情况,派生类对象不会直接包含基类对象的字段,而是通过一个 ...

最新文章

  1. MySQL内核月报 2015.01-MySQL · 捉虫动态· InnoDB自增列重复值问题
  2. C#算数运算符、关系运算符、逻辑运算符、语句
  3. Javascript 中 Array.push 要比 Array.concat 快 945 倍
  4. 【TensorFlow】TensorFlow从浅入深系列之七 -- 教你使用验证数据集判断模型效果
  5. ethtool 原理介绍和解决网卡丢包排查思路(附ethtool源码下载)
  6. UVa1401 Remember the Word(DP+Trie树)
  7. Tomcat端口占用的处理方式
  8. 导入 theano 失败。“cannot import name gof”
  9. 【转】Intellij Idea识别Java Web项目
  10. 深度学习之学习(1-3)Bag of Features (BOF)图像检索算法
  11. 基于海思平台与QT框架的高效视频显示
  12. html5以图片为背景的代码,HTML5画布背景图片
  13. 小程序中使用web-view链接H5网页
  14. 北大数学系「扫地僧」韦东奕爆红!拒绝哈佛offer,留任北大
  15. 《The Science of Scientific Writing》读书笔记
  16. Bzoj1823 [JSOI2010]满汉全席
  17. 什么是 ARIMA模型
  18. RDKit|分子指纹提取、相似性比较及应用
  19. java双色球机选五注代码
  20. 如何选择好公司和计算好价格

热门文章

  1. Linux Red Hat 6.0 配置网卡ip地址和备用ip地址
  2. PHPWind的版权等信息去除的方法
  3. nyist 132Prime Ring Problem
  4. Apache 与 Nginx
  5. hdu-3342-Legal or Not--拓扑排序//两种解法
  6. Ubuntu16.04LTS Install Intel® RealSense™ ROS from Sources
  7. MYSQL5.7.17设置初始密码
  8. 数据分析方法论2——流量分析
  9. 修改默认runlevel
  10. Java POI 导出EXCEL经典实现 Java导出Excel