C++ 类继承的对象布局

C++多重继承下,对象布局与编译器,是否为虚拟继承都有很大关系,下面将逐一分析其中的差别,相同点为都按照类继承的先后顺序布局(类内按照虚表、成员声明先后顺序排列)。该类情况为子类按照继承顺序排列,如class C:public B,public A此种情况布局如下:

如果class B,A带有虚函数,情况又发生了变化:

考虑下面的情况:

class D {
	int d;
public:
	virtual void fun() {}
	virtual ~D() {}
};

class E {
public:
	virtual void fun() {}
	virtual ~E() {}
private:
	int e;
};

class F:public D,public E {
public:
	void fun() {D.fun();}
	virtual ~F() {}
private:
	int f;
};

类F函数的布局中无虚表(vs2010,20个字节):

class F	size(20):
	+---
	| +--- (base class D)
 0	| | {vfptr}
 4	| | d
	| +---
	| +--- (base class E)
 8	| | {vfptr}
12	| | e
	| +---
16	| f
	   +---

g++ 编译器中包含虚表:

Class F    	size=20 align=4   	base size=20 base align=4    F (0x2ea1740) 0
    vptr=((& F::_ZTV1F) + 8u)
D (0x2e76b28) 0
    primary-for F (0x2ea1740)
E (0x2e76b60) 8
    	   vptr=((& F::_ZTV1F) + 28u)

2)  当出现基类重复继承情况

如非虚拟继承,重复继承,编译器会产生告警信息:

warning: direct base ‘D‘ inaccessible in ‘G‘ due to ambiguity [enabled by default]

编译器产生的布局与情况1)相同,都是按照继承的顺序排列

3) 虚拟继承(非菱形继承)

基类若非包含继承同一个父类,虚拟继承实际不存在,考虑下面的继承体系:

类F继承自类D,类G虚拟继承D,F,类的布局图如下(与情况1相同):

g++ -fdump-class-hierarchy -c multiInherit.cpp
Class H
   size=32 align=4
   base size=4 base align=4
H (0x2372b80) 0 nearly-empty
    vptridx=0u vptr=((& H::_ZTV1H) + 16u)
  D (0x231b9d8) 4 virtual
      vptridx=4u vbaseoffset=-12 vptr=((& H::_ZTV1H) + 40u)
  F (0x2372bc0) 12 virtual
      vptridx=8u vbaseoffset=-16 vptr=((& H::_ZTV1H) + 68u)
    D (0x231ba10) 12
        primary-for F (0x2372bc0)     	   E (0x231ba48) 20         	   vptridx=12u vptr=((& H::_ZTV1H) + 88u)

vs2010 编译布局与cl -d1reportAllClassLayout multiInherit.cpp,与类G布局不同,类H这里产生了虚表:

class H	size(32):
	+---
 0	| {vbptr}
	+---
	+--- (virtual base D)
 4	| {vfptr}
 8	| d
	+---
	+--- (virtual base F)
	| +--- (base class D)
12	| | {vfptr}
16	| | d
	| +---
	| +--- (base class E)
20	| | {vfptr}
24	| | e
	| +---
28	| f
	+---

4) 虚拟继承(菱形继承)

菱形继承情况如下

无论类K是否虚拟自I,J,g++产生的类布局都相似(基类都产生虚表,不过地址指向同一内存):

g++ -fdump-class-hierarchy -c multiInherit.cpp
Class K
   size=16 align=4
   base size=8 base align=4
K (0x237f340) 0
    vptridx=0u vptr=((& K::_ZTV1K) + 12u)
  I (0x237f380) 0 nearly-empty
      primary-for K (0x237f340)
      subvttidx=4u
    D (0x231bc40) 8 virtual
        vptridx=20u vbaseoffset=-12 vptr=((& K::_ZTV1K) + 56u)
  J (0x237f3c0) 4 nearly-empty
      subvttidx=12u vptridx=24u vptr=((& K::_ZTV1K) + 32u)
D (0x231bc40) alternative-path

类L的布局如下:

Class L
   size=16 align=4
   base size=4 base align=4
L (0x237f680) 0 nearly-empty
    vptridx=0u vptr=((& L::_ZTV1L) + 24u)
  I (0x237f6c0) 0 nearly-empty virtual
      primary-for L (0x237f680)
      subvttidx=16u vptridx=4u vbaseoffset=-20
    D (0x231bd58) 4 virtual
        vptridx=8u vbaseoffset=-12 vptr=((& L::_ZTV1L) + 48u)
  J (0x237f700) 12 nearly-empty virtual
      subvttidx=24u vptridx=12u vbaseoffset=-24 vptr=((& L::_ZTV1L) + 76u)
    D (0x231bd58) alternative-path

VS 2010产生的类布局与是否虚拟继承有很大关系,未虚拟继承将不产生虚表且相同基类排列在子类下面而非其子类的父类下:

cl -d1reportAllClassLayout multiInherit.cpp
class K	size(16):
	+---
	| +--- (base class I)
 0	| | {vbptr}
	| +---
	| +--- (base class J)
 4	| | {vbptr}
	| +---
	+---
	+--- (virtual base D)
 8	| {vfptr}
12	| d
	+---

类L的布局产生了虚表,且重新排列了基类顺序:

class L	size(20):
	+---
 0	| {vbptr}
	+---
	+--- (virtual base D)
 4	| {vfptr}
 8	| d
	+---
	+--- (virtual base I)
12	| {vbptr}
	+---
	+--- (virtual base J)
16	| {vbptr}
	+---
时间: 2024-09-30 00:00:52

C++ 类继承的对象布局的相关文章

C++ 类继承与对象赋值 情况下 成员变量的覆盖 浅析

[摘要] 类的继承以及对象的赋值会带来成员变量的相互传递.这里详细讨论了,类间继承带来的成员变量的传递采用覆盖原则,采用函数级的成员变量的取值:对象赋值带来的成员变量的传递采用,实函数采用数据类型的实函数,虚函数采用赋值源的虚函数,成员变量采用赋值源的成员变量,其实也是函数级的成员变量. [正文] 在类继承中,成员变量存在覆盖的情况,成员函数则存在隐藏和覆盖以及重载的情况.在类继承中,公有继承会导致公有成员变量的覆盖,从而使得成员函数的调用出现各种结果. [代码示例 01] #include<i

【JavaScript】类继承(对象冒充)和原型继承__深入理解原型和原型链

JavaScript里的继承方式在很多书上分了很多类型和实现方式,大体上就是两种:类继承(对象冒充)和原型继承. 类继承(对象冒充):在函数内部定义自身的属性的方法,子类继承时,用call或apply实现对象冒充,把类型定义的东西都复制过来,这样的继承子类与父类并没有多少关联,不互相影响,有利于保护自身的一些私有属性. 原型继承:每个函数都有自己的原型(prototype)属性,这个属性是在生成实例对象时自动创建的.它本身又是一个对象,拥有能够在实例间共享的属性和方法.而实例本身的属性和方法,则

C++类继承中内存的布局

1 前言       了解你所使用的编程语言究竟是如何实现的,对于C++程序员可能特别有意义.首先,它可以去除我们对于所使用语言的神秘感,使我们不至于对于编译器干的活感到完全不可思议:尤其重要的是,它使我们在Debug和使用语言高级特性的时候,有更多的把握.当需要提高代码效率的时候,这些知识也能够很好地帮助我们. 本文着重回答这样一些问题: 1* 类如何布局? 2* 成员变量如何访问? 3* 成员函数如何访问? 4* 所谓的"调整块"(adjuster thunk)是怎么回事? 5*

对【面向对象的类访问和对象访问的区别【this以及类访问】、静态成员的访问区别、类常量等、继承和重写、访问修饰限定符、冒泡排序】的总结

面向对象的总结1.首先面向对象的编程使用的都是使用对象作为处理对象而言[例如继承等情形,继承的本质来说,依旧针对的是对象]但是只有针对类的访问.静态成员以及访问修饰限定符时,针对的是类的概念 2.类内定义时,只有五种情形:类常量[const关键字定义并且使用类来调用]属性[这些属性和方法是针对该类实例的对象来调用]方法[在这种情形下使用$this进行指代作用环境(调用该方法的对象),只有继承中,子类实例的对象会向下传递到静态调用里]静态属性[用来作为实例该类的所有对象之间共享的数据.保存在类中]

面向对象的类访问和对象访问的区别【this以及类访问】、静态成员的访问区别、类常量等、继承和重写、访问修饰限定符、冒泡排序

1.mysql封装类 在构造函数中定义初始化数据库连接的相关参数[因为实例化对象时传入的参数是该对象的唯一参数入口]但是不需要再构造函数中进行冗余定义,而是在构造函数中进行调用类中的其他方法的方式来实现构造函数的设置工作[这样的模块分离使逻辑更加简单] [重点]2.静态成员相当于每次实例化对象之间是有关系的[例如计数功能]因为每次实例化类时创建的对象占用的空间都是新创建的,这一点需要注意,所以同一个类下的对象之间的变量并没有交互的效果.[回想起当初函数中的静态局部变量的使用][延生到静态全局变量

虚继承之单继承的内存布局(VC在编译时会把vfptr放到类的头部,这和Delphi完全一致)

C++2.0以后全面支持虚函数与虚继承,这两个特性的引入为C++增强了不少功能,也引入了不少烦恼.虚函数与虚继承有哪些特性,今天就不记录了,如果能搞了解一下编译器是如何实现虚函数和虚继承,它们在类的内存空间中又是如何布局的,却可以对C++的了解深入不少.这段时间花了一些时间了解这些玩意,搞得偶都,不过总算有些收获,嘿嘿. 先看一段代码class A{      virtual aa(){};}; class B : public virtual  A{      char j[3];      

理解组合对象与类继承

1.概念解析 继承:"继承"是面向对象编程中的一个概念. 面向对象编程的单一职责原理(SPR-Single Responsibility Principle)规定对象只能有一个职责.就一个类而言,应该仅有一个引起它变化的原因. 为什么要把不同的职责分配到不同的类中呢?因为每一个职责都是变化的一个轴线,当需求变化时,该变化会反映为类的职责的变化.如果一个类承担了多于一个的职责,那么就意味着引起它的变化的原因会有多个.如果一个类承担的职责过多,那么就等同于把这些职责耦合在了一起.一个职责的

【C++】通过基类的指针变量访问派生类中由基类继承来的隐藏对象

//<img src="http://img.blog.csdn.net/20150512213309005?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZG91ZG91d2ExMjM0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /> // 可以将一个派生类的对象的地址赋给其基类的指针变量,但

递归函数、二分查找、面相对象初识、类空间,对象空间、组合、继承

一.递归函数 一般递归100多次,都没有解决的问题,放弃递归. 默认递归深度:998 修改默认递归次数 import sys sys.setrecursionlimit(100000) #设置十万次 count = 0 def func1(): global count count += 1 print(count) func1() func1() 用递归 解决一个年龄问题. alex 他比佩奇 大两岁.  4   age(3) + 2 佩奇 他比日天 大两岁.  3   age(2) + 2