本文有更新,请移步我的个人博客:https://blog.andyqiao.top/article/18/

主要从三个方面来讲:

  1 单一继承

  2 多重继承

  3 虚拟继承

1 单一继承

(1)派生类完全拥有基类的内存布局,并保证其完整性。

  派生类可以看作是完整的基类的Object再加上派生类自己的Object。如果基类中没有虚成员函数,那么派生类与具有相同功能的非派生类将不带来任何性能上的差异。另外,一定要保证基类的完整性。实际内存布局由编译器自己决定,VS里,把虚指针放在最前边,接着是基类的Object,最后是派生类自己的object。举个栗子:

class A
{
int b;
char c;
};
class A1 :public A
{
char a;
};
int main()
{
cout << sizeof(A) << " " << sizeof(A1) << endl;
return ;
}

  输出是什么?

  答案:

    8 12

  A类的话,一个int,一个char,5B,内存对齐一下,8B。A1的话,一个int,两个char,内存对齐一下,也是8B。不对吗?

  我说了,要保证基类对象的完整性。那么一定要保证A1类前面的几个字节一定要与A类完全一样。也就是说,A类作为内存补齐的3个字节也是要出现在A1里面的。也就是说,A类是这样的:int(4B)+char(1B)+padding(3B)=8B,A1类:int(4B)+char(1B)+padding(3B)+char(1B)+padding(3B)=12B。

(2)虚指针怎么处理?

  还是视编译器而定,VS是永远把vptr放在对象的最前边。如果基类中含有虚函数,那么处理情况与上边一样。可是,如果基类中没有虚函数而派生类有的话,那么如果把vptr放在派生类的前边的话,将会导致派生类中基类成分并不在最前边。这将带来什么问题呢?举栗:假设A不含虚,而A1含。

A *pA;
A1 obj_A1;
pA=&obj_A1;

  如果A1完全包含A并且A位于A1的最前边,那么编译器只需要把&obj_A1直接赋给pA就可以了。如果不是呢?编译器就需要把&obj_A1+sizeof(vptr)赋给pA了。

2 多重继承

  说结论:VS的内存布局是按照声明顺序排列内存。再举个栗子:

class point2d
{
public:
virtual ~point2d(){};
float x;
float y;
};
class point3d :public point2d
{
~point3d(){};
float z;
};
class vertex
{
public:
virtual ~vertex(){};
vertex* next;
};
class vertex3d :public point3d, public vertex
{
float bulabula;
}; int _tmain(int argc, _TCHAR* argv[])
{
cout << sizeof(point2d) << " " << sizeof(point3d) << " " << sizeof(vertex) << " " << sizeof(vertex3d) << endl;
return ;
}

  输出: 12 16 8 24。

  内存布局:

    point2d: vptr(4)+x(4)+y(4)=12B

    point3d: vptr+x+y+z=16B

    vertex: vptr+next=8B

    vertex3d: vptr+x+y+z+vptr+next+bulabula=28B

为什么需要多个虚指针?请往下看。

3 虚拟继承

(1)为什么要有“虚继承”这样的机制?

  简单讲,虚继承是为也防止“diamond”继承所带来的问题。也就是类A1、A2都继承于A,类B又同时继承于A1、A2。这样一来,类B中就有两份类A的成员了,这样的程序无法通过编译。我们改成这样的形式:

class A
{
public:
  int a;
  virtual ~A();
virtual void fun(){cout<<"A"<<endl;}
};
class A1 :public virtual A
{
public:
  int a1;
virtual void fun(){cout<<"A1"<<endl;}
};
class A2 :public virtual A
{
public:
  int a2;
virtual void fun(){cout<<"A2"<<endl;}
}; class B :public A1,public A2 {
public:
  int b;
virtual void fun(){cout<<"B"<<endl;}
virtual void funB(){};
};

  这样就能防止这样的事情发生。

(2)虚拟继承与普通继承的区别:

  普通继承使得派生类每继承一个基类便拥有一份基类的成员。而虚拟继承会把通过虚拟继承的那一部分,放在对象的最后。从而使得只拥有一份基类中的成员。虚拟对象的偏移量被保存在Derived类的vtbl的this指向的上一个slot。比较难理解。下面我给你个栗子。

(3)虚拟继承的内存布局:

  每个派生类会把其不变部分放在前面,共享部分放在后面。

上面四个类的大小是怎样的呢?

int _tmain(int argc, _TCHAR* argv[])
{
cout << sizeof(A) << " " << sizeof(A1) << " " << sizeof(A2) << " " << sizeof(B) << endl;
return ;
}

  输出:8 16 16 28

  内存布局:

    A: vptr+a=8B

    A1: vptr+a1+vptrA+a=16B

    A2: vptr+a2+vptrA+a=16B

    A3: vptr+a1+vptrA2+a2+b+vptrA+a=28B

  上个草图:

  那究竟为什么需要多个虚指针?将对象内存布局和虚表结构搞清楚之后,答案是不是呼之欲出呢?

是的,因为这样可以保证在将子类指针/引用转换成基类指针时编译器可以直接根据对像的内存布局进行偏移,从而使得指向的第一个内容为虚指针,进而实现多态(根据静态类型执行相应动作)。

C++中派生类对象的内存布局的更多相关文章

  1. c++类对象的内存分布

    要想知道c++类对象的内存布局, 可以有多种方式,比如: 1)输出成员变量的偏移, 通过offsetof宏来得到 2)通过调试器查看, 比如常用的VS 1.没有数据成员的对象 class A{ }; ...

  2. HotSpot源码分析之C++对象的内存布局

    HotSpot采用了OOP-Klass模型来描述Java类和对象.OOP(Ordinary Object Pointer)指的是普通对象指针,而Klass用来描述对象的具体类型.为了更好理解这个模型, ...

  3. VS中C++对象的内存布局

    本文主要简述一下在Visual Studio中C++对象的内存布局,这里没有什么测试代码,只是以图文的形式来描述一下内存分布,关于测试的代码以及C++对象模型的其他内容大家可以参考一下陈皓先生的几篇博 ...

  4. JVM中对象的内存布局与访问定位

      一.对象的内存布局 已主流的HotSpot虚拟机来说,   在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header).实例数据(Instance Data)和对齐填 ...

  5. 基类中定义的虚函数在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型及参数的先后顺序,都必须与基类中的原型完全相同 but------> 可以返回派生类对象的引用或指针

      您查询的关键词是:c++primer习题15.25 以下是该网页在北京时间 2016年07月15日 02:57:08 的快照: 如果打开速度慢,可以尝试快速版:如果想更新或删除快照,可以投诉快照. ...

  6. C++中的类所占内存空间总结

    C++中的类所占内存空间总结    最近在复习c++的一些基础,感觉这篇文章很不错,转载来,大家看看! 类所占内存的大小是由成员变量(静态变量除外)决定的,成员函数(这是笼统的说,后面会细说)是不计算 ...

  7. C++ 对象的内存布局(上)

    C++ 对象的内存布局(上) 陈皓 http://blog.csdn.net/haoel 点击这里查看下篇>>> 前言 07年12月,我写了一篇<C++虚函数表解析>的文 ...

  8. JVM——深入分析对象的内存布局

    概述 一个对象本身的内在结构需要一种描述方式,这个描述信息是以字节码的方法存储在方法区中的.Class本身就是一个对象,都以KB为单位,如果new Integer()为了表示一个数据就占用KB级别的内 ...

  9. Java对象的内存布局

    对象的内存布局 平时用java编写程序,你了解java对象的内存布局么? 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域: 对象头 实例数据 对齐填充 对象头 对象头包括两部分信息: ...

随机推荐

  1. HMM 自学教程(一)引言

    本系列文章摘自 52nlp(我爱自然语言处理: http://www.52nlp.cn/),原文链接在 HMM 学习最佳范例,这是针对 国外网站上一个 HMM 教程 的翻译,作者功底很深,翻译得很精彩 ...

  2. SQL Server中的事务日志管理(3/9):事务日志,备份与恢复

    当一切正常时,没有必要特别留意什么是事务日志,它是如何工作的.你只要确保每个数据库都有正确的备份.当出现问题时,事务日志的理解对于采取修正操作是重要的,尤其在需要紧急恢复数据库到指定点时.这系列文章会 ...

  3. [团队项目]expat不兼容BUG

    本周五软工团队项目的第一次前后端全部对接时,出了一个蛋疼的错误. 最初起因是小丽叔出于安全的考虑,使用守护进程来跑Web服务器.守护进程(Daemon)是运行在后台的一种特殊进程,如果服务器用root ...

  4. Array 数组常用方法

    (1)基本的数组方法 1.join() Array.join()方法将数组中所有元素都转化为字符串并连接在一起,返回最后生成的字符串.可以自己指定分隔的符号,如果不指定,默认使用逗号 var arr ...

  5. SQL交叉表

    之前做货品横向展示时,有看到评论说用到交叉表. 公司最近需要给订单表做一个数据汇总的功能,同事给到一个参考SQL select * from (select COUNT(1) as 已锁定 from ...

  6. 从苹果apns的feedback服务器获取推送失败的token

    在开发自己的苹果推送服务时候,要合理的控制ios设备的Token,而这个Token是由苹果服务器Apns产生的,就是每次app问Apns要Token,由苹果服务器产生的Token会记录到Apns里面, ...

  7. [PE结构分析] 6.IMAGE_SECTION_HEADER

    IMAGE_SECTION_HEADER 的源代码如下: typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAM ...

  8. CodeBlocks VS2015编译环境设置

    1. 菜单 Settings --> Compiler... 2. 设置vs 的安装路径

  9. 【iOS】Quartz2D简单介绍

    一.什么是Quartz2D Quartz 2D是⼀个二维绘图引擎,同时支持iOS和Mac系统 Quartz 2D能完成的工作: 绘制图形 : 线条\三角形\矩形\圆\弧等 绘制文字 绘制\生成图片(图 ...

  10. Follow me to learn what is repository pattern

    Introduction Creating a generic repository pattern in an mvc application with entity framework is th ...