引例:

class X{};
class Y:public virtual X{};
class Z:public virtual X{};
class A:public Y,public Z{};
X Y Z A类对象的大小是多少?? 
1> 没有提供empty virtual base特殊支持的编译器:1 8 8 12
2> 提供了empty virtual base特殊支持的编译器:1 4 4 8
 
    一个class的data members,一般而言,可以表现这个class在程序执行时的某种状态。
    nonstatic data members放置的是“个别的class object”感兴趣的数据。 C++对象模型把nonstatic data members直接放置在每一个class object之中,对于继承而来的nonstatic data members也是如此。
    static data members则放置的是“整个class”感兴趣的数据。C++对象模型则把static data members,则放置在程序的一个global data segment中,不会影响个别class object的大小。
    每一个class object因此必须有足够的大小容纳它所有的nonstatic data members。但有时候,它可能比想象的还大,原因是:
1> 由编译器自动加上的额外data members,用以支持某种语言特性(主要是各种virtual特性-虚函数、虚基类​)
2> 因为alignment(字节对其调整)

一 Data member的存取

static data member

1 static data member被编译器提出class之外,视为一个global变量(但只在class声明范围之内可见)。
2 若取一个static data member的地址,会得到一个指向其数据类型的指针,而不是一个指向其class member的指针。
如:&Point3d::chunkSize;   获得的内存地址是 int *
3 每一个static data member只有一个实体,存放在程序的data segment之中,每次程序取用static member,就会被内部转化为对唯一的extern实体的直接取用操作。如:
//origin.chunkSize=250;
Point3d::chunkSize=250;
//pt->chunkSize=250;
Point3d::chunkSize=250;
注:这(静态数据成员)是c++语言中"通过一个指针和通过一个对象来存取member,结论完全相同"的唯一一种情况。
4 如果多个class,都声明了一个相同的static data member,那么当他们被放在程序的data segment时,就会导致命名冲突。编译会暗中对每一个static data member编码(即:name-mangling)以获得一个独一无二的程序识别码。

nonstatic data members

1 nonstatic data members直接存放在每一个class object之中,除非经由明确(explicit)或隐式的(implicit)class object,没有办法直接存取他们。
2 只要程序员在一个member function中直接处理一个nonstatic data member,所谓“implicit class object”(即:this指针)就会发生。
3 欲对一个nonstatic data member进行存取操作,编译器需要把class object的起始地址加上data member的偏移量。每一个nonstatic data member的偏移量offset在编译时期即可获得。(因此,存取一个nonstatic data member的效率和存取一个C struct member或一个nonderived class的member是一样的)
4 取一个nonstatic data member的地址,将会得到它在class中的偏移量(offset);取一个绑定于真正class object身上的data member的地址,将会得到该member在内存中的真正地址。

二 “继承”与data member

    在c++继承模型中,一个derived class object所表现出来的东西,是其自己的members加上其base calss members的总和。在大部分编译器中,base class members总是先出现,但属于virtual base class的除外。
    derived class object中数据成员的布局与继承、virtual function、virtual base等情况有关。下面分几种情况进行讨论:

1 无继承、无virtual function

    这种情况和C struct完全一样。如下图:
         
 

2 只要继承不要多态(单一继承、不含virtual function)

    一般而言,具体继承并不会增加空间或存取时间上的额外负担。

    这样设计的好处是可以把管理x和y坐标的程序代码局部化。此外这个设计可以明显表现出两个抽象类之间的紧密关系。
    但这样设计,容易犯两类错误:
    1> 经验不足的人可能会重复设计一些相同的函数。
    2> 把一个class分解成两层或更多层,有可能会为了“表现class体系之抽象化”而膨胀所需要空间。c++语言保证“出现在derived class 中的base class subobject有其完整原样性”。举一个例子:

 
              具体类                                               分裂为三层结构
    为了保证"出现在derived class 中的base class subobject有其完整原样性",三层结构可能的布局如图所示:
    然而,如果C++语言把derived class members(也就是concrete2::bit2或concrete3::bit3)和concrete1 subobject捆绑在一起,去掉填补空间,则在如下图语义中,base class subobject的完整原样性就无法保证(但是gcc却采用的正在用方式):

3 单一继承加多态(即:含有虚函数)

    如果我们要处理一个坐标点,而不打算在乎它是一个point2d或point3d实例(也就是我们企图以多态的方式处理2d或3d坐标点),那么我们要在继承关系中提供一个virtual function接口。为了支持这样的特性,势必会给我们的Point2d class带来空间和存取时间上的额外负担:
1> 导入一个和Point2d有关的virtual table,用来存放它所声明的每一个virtual functions的地址。
2> 在每一个class object中导入一个vptr,提供执行期链接,是每一个object能找到相应的virtual table。
3> 加强版constructor,使它能够为vptr设定初值,让它指向class所对应的virtual table。
4> 加强版destructor,使它能够抹消"指向class之相关virtual table"的vptr。
    加上多态后,对于每一个对象在空间上的负担就是多了一个vptr指针的空间(通常是一个word,4byte)。然而这个vptr放在类对象的什么位置最好?有两种主流设计:一种放在前端;一种放在尾端。
cfront编译器放在class object的尾端(好处:可以保证base class c struct的对象布局):
    到了c++2.0后,某些编译器(gcc 和 vc6.0都是这样的)开始把vptr放到class object的前端。(好处是与class vptr之间的offset不需要专门准备;缺点是丧失了c语言的兼容性)。
    单一继承加多态后的对象布局(vptr放在尾端的情况):

 

4 多重继承

    单一继承提供了一种自然多态。base class和derived class的objects都是从相同的地址开始的,其差异只在于derived object(可能)比较大。把一个derived class object指定给base class(不管继承深度有多深)的指针或引用,该操作并不需要编译器去调停或修改地址。它很自然的可以发生,而且提供了最佳执行效率。
    多重继承既不像单一继承,也不容塑造出其模型:
1> 多重继承的复杂度在于derived class和其第二个或后继base class之间的非自然关系。
2> 多重继承的问题主要在与derived class和其第二个或后继base class objects之间的转换:
① 对一个多重派生对象,将其地址指定给“最左端base class的指针”,情况和单一继承时相同。(二者指向相同的地址,只需地址指定操作而已)。
② 至于第二个或后继的base class的地址指定操作,则需要将地址修改,加上(或减去)介于中间的base class subobjects大小。
实例:
Vertex3d v3d;
Vertex *pv;
Point2d *p2d;
Point3d *p3d;
1> pv=&v3d;//被转化为:
pv=(Vertex *)(((char*)&v3d)+sizeof(Point3d));
2> 而下面的指定操作:
p2d=&v3d;
p3d=&v3d;
都只需要简单拷贝其地址就行了。
3> c++标准并没有要求Vertex3d中base class Point3d和Vertex有特定的排列次序。但现在大多数编译器还是想cfront那样根据声明次序排列他们。
4> members的位置在编译时就固定了,所以存取第二个(或后继)base class中的一个data member不需要额外的成本,只是一个简单的offset运算,就像单一继承一样简单-不管是经由一个指针、一个reference或是一个object来存取。

5 虚拟继承

    多重继承的一个语意上的副作用就是,它必须支持某种形式的“shared subobject继承”。在语言层面的解决办法是导入所谓的虚拟继承。
    一般的实现方法如下所述:class如果内含一个或多个virtual base class subobjects,像istream那样,将被分割为两部分:一个不变局部和一个共享局部。不变局部中的数据,不管如何衍变,总是拥有固定的offset,所以这部分可以直接存取;至于共享局部,所表现的就是virtual base class subobject,这一部分的数据,其位置会因为每次的派生操作而有变化。所以它们只可以间接存取。
    一般的布局策略是先安排好derived class的不变部分,然后再建立其共享部分。然而如何存取class的共享不放呢?
    cfront编译器会在每一个derived class object中安插一些指针,每个指针指向一个virtual base class。要存取继承得来的virtual base class members,可以使用相关指针间接完成。然而还存在如下优化:
1> 理想上我们希望class object有固定的负担。所以每一个对象不应该针对每一个virtual base class背负一个额外的指针(这样负担会导致随虚基类的数目变化而变化)。解决方案:
①Microsoft编译器:引入所谓的virtual base class stable。每一个class object如果有一个或多个virtual base classes就会有编译器安插一个指针,指向virtual base class stable。至于真正的virtual base class指针被放置在virtual base class stable中。
②在virtual function table中放置virtual base class的offset(而不是地址)。
2> 理想上我们希望有固定的存取时间。解决方案:
大部分编译器是经由拷贝操作取的所有的nested virtual base class指针,放到derived class object之中。
 
对象布局:

 
1 以指针指向base class的实现模型
2 使用virtual table offset strategy所产生的数据布局

 
 

 
 
 
 
 

data语意学的更多相关文章

  1. 《深度探索c++对象模型》chapter3 Data语意学

    一个空的class:如 class X{} ; sizeof(X)==1; sizeof为什么为1,他有一个隐晦的1 byte,那是被编译器安插进去的一个char,这使得class2的两个object ...

  2. c++ data语意学

     Data Member的绑定 extern float x; class Point3d { public: point3d(); //问题:被传回和被设定的x是哪一个x呢? float X() c ...

  3. 《深度探索C++对象模型》笔记——Data语意学

    Data Member的绑定 inline member functin躯体之内的一个data member绑定操作会在整个class声明完成之后才发生. argument list中的名称还是会在它 ...

  4. 第3章 Data语意学

    在C++中经常会遇到一个类的大小问题,关于一个类的大小一般受到三个方面的影响. 语言本身所造成的额外负担,如在虚拟继承中会遇到如派生类中会包含一个指针指向base class subobjec,这样会 ...

  5. 【C++】深度探索C++对象模型读书笔记--Data语意学(The Semantics of data)

    1. 一个空类的大小是1 byte.这是为了让这一类的两个对象得以在内存中配置独一无二的地址. 2. Nonstatic data member 放置的是“个别的class object”感兴趣的数据 ...

  6. c++对象模型之Data布局

    Data语意学 class X{}; class Y : publicvirtual X {}; class Z : publicvirtual X {}; class A : publicY, pu ...

  7. inside the C++ Object model总结

    一. 关于对象 1.内联函数:能够除去函数调用的开支,每一处内联函数的调用都是代码的复制.这是一种空间换取时间的做法,若函数代码量大或者有循环的情况下,不宜内联(这件事有些编译器会自动帮你做).在类中 ...

  8. 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记

    转载:http://dsqiu.iteye.com/blog/1669614 第一章 关于对象 使用class封装之后的布局成本: class并没有增加成本,data members直接内含在每一个c ...

  9. 《深度探索C++对象模型》1

    C++对象模型: 多重继承模型示意: 第二章:构造函数 语意学 基类和派生类: Bear yogi; ZooAnimal franny=yogi; 在这里,很容易理解合成的copy构造函数将vptr指 ...

随机推荐

  1. http协议的各个版本及区别

      这个东西在一篇博文上面看见的,谢谢原博主提供!又学习了...

  2. 2014-08-05 再次接触VBA

    今天是在吾索实习的第20天.本来今天的计划是完成BBS的界面的设计的,但是中途发生了一些小插曲,经理要求我帮忙用VBA实现EXCEL中表与表之间的动态联编,而且答应了客户明天就要看到成品了.所以只好放 ...

  3. Android输入法界面管理(打开/关闭/状态获取)

    最近做一个带发表情的聊天界面,需要管理系统输入法的状态, 一.打开输入法窗口: InputMethodManager inputMethodManager = (InputMethodManager) ...

  4. jzp线性筛及其简单应用

    前言: 很久以前看过了线性筛,没怎么注意原理,但是后来发现线性筛还有很有用的.. 比如上次做的一道题就需要找出每个数的最小质因子,先筛再找就太慢了..一看线性筛发现就可以直接在筛的过程中处理出来了! ...

  5. 给大家推荐一款代替Visio的在线作图工具ProcessOn

    过去作图的时候一直都是在用visio,每一次换了电脑使用都要重新安装,这大家都知道,最头疼的就是激活问题,曾经因为激活问题我“找遍了”正个互联网,最后还没找到...从08年开始到现在,visio用了这 ...

  6. DVP

    债券结算方式是指在债券结算业务中,债券的所有权转移或权利质押与相应结算款项的交收这两者执行过程中的不同制约形式.中央债券簿记系统中所设计的结算方式有:纯券过户.见券付款.见款付券.券款对付(DVP)四 ...

  7. Java基础知识强化41:StringBuffer类之StringBuffer的反转功能

    1. StringBuffer 的反转功能: public StringBuffer reverse(): 2. 案例演示: package cn.itcast_05; /* * StringBuff ...

  8. Mac下Qt连接MySQL 驱动问题

    Mac OS X下Qt的mySQL driver编译安装 原创文章,采用CC协议发布,转载请注明: 转载自canX.me 本文链接地址: Mac OS X下Qt的mySQL driver编译安装 – ...

  9. 解决:用PivotGridControl 与 chartControl 配合使用,Series最大只显示10条

    修改 PivotGridControl  控件的 OptionsChartDataSource.MaxAllowedSeriesCount 的值就可以了  默认为10条

  10. Android -------- 用XmlPullParser解析器解析XML文件