zoukankan      html  css  js  c++  java
  • c++对象模型研究3:数据

    开始前先看看

    class X {};
    class Y:public virtual X {};
    class Z:public virtual X {};
    class A:public Y,public Z {};


    上述X,Y,Z,A中没有任何一个class内含明显的数据,其间只表示了继承关系。

    按照书上的例子

    sizeof X = 1
    sizeof Y = 8
    sizeof Z = 8
    sizeof A = 12

    译注是

    sizeof X = 1
    sizeof Y = 4
    sizeof Z = 4
    sizeof A = 8


    先看X,事实上并不是空的,编译器会安插进去一个char。使得这个class的对象在内存中配置独一无二的地址。

    至于Y和Z受到三个因素的影响:

    1.语言本身所造成的额外负担。其实就是之前一直说的virtual问题。

    2.编译器对于特殊情况所提供的优化处理。有些编译器会对这个1bytes作出不同的处理(例如省略)。

    3.Alignment的限制,我的理解是字节对齐,在大部分机器上,群聚的结构体大小会受到alignment的限制,使它们能够更有效率地在内存中被存取。

    ps:一个虚基类对象只会在派生类中存在一份实体,不管它在class继承体系中出现了多少次。

    C++对象模型尽量以空间优化和存取速度优化的考虑来表现非static成员数据,并且保持和C语言struct数据配置的兼容性。它把数据直接存放在每一个类对象之中。对于继承而来的非static成员数据(不管是virtual还是非virtual基类)也是如此。而类的static成员数据是存放在全局中,只有一份实例(甚至即使该class没有任何对象实体,其static成员数据也已存在),但是一个template类的static成员数据的行为稍有不同。

    每一个类对象必须有足够的大小以容纳它所有的非static成员数据,它可能比你想象的还大,原因是:

    1.由编译器自动加上的额外成员数据,用以支持某些语言特性(主要是各种virtual特性)。

    2.因为alignment的需要。

    成员数据的绑定

    先说说extern:

    extern 表明该变量在别的地方已经定义过了,在这里要使用那个变量.。例如变量在xxx.cpp里面定义过了,现在在本头文件中,以extern int a; 的形式声明,那么include"本头文件"的cpp,都可以使用该变量。extern与(const&static)不同,(const和static只在本模块中起作用) extern可以在其他模块中起作用。

    如果成员函数没参数,或者不闲的蛋疼把两个类型定义成同个关键字typedef。那么typedef或者成员数据放前放后都一样;如果出现了以上状况,那么必须把内联typedef 放在成员函数的参数之前。这个主要注意的是内联typedef声明需要放在类的起始处,而其他成员函数里面的数据,可以放在类里面的任何地方。

    成员数据的布局

    非static成员数据在类对象中的排列顺序将和其被声明的顺序一样,任何中间介入的static成员数据都不会被放进对象布局之中。

    c++ standard要求,同一个access section(访问级别)中,成员的排列只需要符合“较晚出现的成员在对象中有较高的地址”即可,并不一定要连续排列。原因之一是边界调整,原因之二是插入的关于virtual的东西,如vptr。

    c++ standard也允许将多个access sections中的成员数据自由排列,不必在乎声明次序(是指access sections之间的自由排列),但当前各家编译器都是把一个以上的access section连起来,依照声明次序,称为一个连续区块。另外access sections的多寡不会招来额外的负担。  

    成员数据的存取

    static成员数据:在内存中只有一份实体,所以用什么方式,无论是指针也好,对象也罢;本子类的也好,从祖祖父继承来的也罢,都一样其存取路径还是一样的直接。

    非static成员数据:直接存放在每个对象中,所以只能由对象来对他们进行存取操作。对象分为explicit class object(自己定义的)和implicit class object(由this指针表达这个implicit class object)(成员函数中访问成员数据时用的)。

    从object origin存取”和“从pointer pt存取”的区别:

    如果是关于virtual,这里就涉及多态的概念。如果用pt,那么我们不能说pt必然指向哪个class type(因此我们也就不知道编译时期这个成员真正的offset)所以这个存取操作必须延迟至执行期,经由一个额外的间接导引,才能解决。但如果用“.”那么class type就确定无疑了,也就没那么多的事儿了。

    继承与成员数据

    单一继承不含virtual函数

    其数据布局是这样的,子类对象总是把从父类对象弄成一个子对象,然后把这个子对象放在自己的成员数据之前。因此,子类通过对象或者通过对象指针访问父类成员不会存在间接性,父类成员在编译期就可以确定其offset值(父类成员在父类中的offset值和在子类中的offset值是一样的,因为子类对象把整个父类对象给扒拉下来直接按头上了)。因为父类对象在子类对象的首部,这样当父类指针被子类赋值时,父类指针仍然指向子类对象的父类部分(子对象)起始地址。
    当然这样的存放方式也是有缺点的(指的是没有虚函数,没有多态的情况)。

    加上多态

    加上多态即虚函数后,首先是virtual table 和vptr 会创建出来,当然这个创建过程会影响到constructor、copy constructor(为vptr设初值)、destructor(结束后删除vptr)。

    至于vptr放在哪里要看具体的编译器,vptr放在尾端,可以兼容c的struct object;vptr放在前端可以避免“从class object起始点开始量起的offset在执行期必须备妥,甚至于class vptr之间的offset也必须备妥”。

    多重继承

    单一继承提供了一种“自然多态”形式,换句话说单一继承可以把子对象一个一个的叠加在derived object上面,based object和derived object 都是从相同地址开始的。所以derived object转based object比较方便(只要改变对象size就行了)。

    虚拟继承

    如果一个class 内含一个或多个virtual based class subobjects,那它将会被分成两个部分:不变的部分和共享的部分。不变的部分不论后继如何衍化,总是拥有固定的offset(从object头算起),所以这部分数据可以被直接存取。共享部分表现出来的就是virtual base class(虚基类) subobject(因为虚基类被多个class继承,也会被多个子object 更改,为了保证虚基类的 object 的统一性,就需要单独把虚基类的 object 给拎出来了),这一部分其位置会因为派生对象操作而发生变化,所以只能被间接存取(引入一个新的指针,指向共享的内容)。
    当然只是单纯的引入指针指向共享的virtual base class subobject的话会存在两个缺点:1是随着虚基类的增加,指针的个数也会增加;2是虚基类之间如果也存在虚继承的话,间接存取的层数也会增加(子对象->父虚基类对象->祖父虚基类对象···)。
    对于第一个问题,有两种解决方案:1是设一个指针指向一张虚基类表,虚基类表中存放虚基类对象访问地址;2是在虚基类表中存放每一个虚基类的offset,而不是地址。

    对象成员的访问效率

    大概的意思是打开优化开关后,都是差不多的,只有虚拟继承的效率比较差。另外都要靠测试来检验,没有什么经验可循,还是看编译器。

    指向成员数据的指针

    指向数据成员的指针,是一个有点神秘又颇有用处的语言特性,特别是如果你需要详细调查类成员的底层布局的话。这样的调查可以用于决定vptr是放在class的起始处或者尾端。另外一个用途是可以用来决定类中的access sections的次序。

    指向成员数据的指针的效率问题

    具体要看编译器怎样优化了

    参考:

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

    http://blog.csdn.net/ChinaJane163/article/details/49894391

  • 相关阅读:
    Linux下sed,awk,grep,cut,find学习笔记
    Python文件处理(1)
    KMP详解
    Java引用详解
    解决安卓中页脚被输入法顶起的问题
    解决swfupload上传控件文件名中文乱码问题 三种方法 flash及最新版本11.8.800.168
    null id in entry (don't flush the Session after an exception occurs)
    HQL中的Like查询需要注意的地方
    spring mvc controller间跳转 重定向 传参
    node to traverse cannot be null!
  • 原文地址:https://www.cnblogs.com/losophy/p/9494625.html
Copyright © 2011-2022 走看看