zoukankan      html  css  js  c++  java
  • 【C++对象模型】第三章 Data语义学

    1、 Data Member 的布局

    • 同一个Access Section(private, public等)中,data member的顺序按照声明顺序排列,但是没有规定需要连续排序。同时编译器可能会安插一些内部的data member(比如vptr),用来支持整个对象模型。
    • 不同Access Section中,member的排列顺序由编译器决定。

    2、Data Member 的存取

      每一个member 的存取许可(private public protected),以及与class的关联,并不会导致任何空间上或执行时间上的额外负担——不论是在个别的class objects 或是在static data member 本身。

    class X {};    //空虚基类          sizeof(X) = 1
    class A: public virtual X {};   //sizeof(A) = 8
    class B: public virtual X {};   //sizeof(B) = 8
    class C: public A, public B {}; //sizeof(C) = 12

      -X是空基类,需要安插一个char,使得class的两个objects在内存上拥有唯一的地址; 
      -Size(A) = 4(为了支持virtual base class而额外增加的指针) + 1(base class本身) + 3(Alignment对齐) = 8 
      -Size(C) = 4(A) + 4(B) + 1(X,虚拟继承被A/B所共享) + 3(对齐) = 12

      static data members 

      直接存放于Data Segment,拥有唯一实体,不存在于class object中;
      如果两个class声明了同名static members,编译器会对class中static data member名字进行修饰,使其独一无二;
      对static data member取址,得到该member数据类型指针;nonstatic data member取址将得到该member在类中偏移。

          nonstatic data members
      欲对一个nonstatic data member 进行存取操作,编译器需要吧class object的起始地址加上data member的偏移量(在编译事情就可以获知)。

    class A {public: int x; int y;};
    A a;
    a.y = 0;  //&a.y = &a + &A::y

    3、继承与Data Member

      3.1 只要继承不要多态

      base class subobject会在derived class中保持原样。 这种情况并不会增加空间或存储时间上的额外负担。这种情况base class和derived class的objects都是从相同的地址开始,其差异只在于derived object 比较大,用以容纳自建的nonstatic data members,把一个derived class object指定给base class 的指针或引用,并不需要编译器去调停或修改地址,它很滋润的可以发生,而且提供了最佳执行效率。

      3.2 加上多态

      这种情况会带来空间和存取时间的额外负担:

      1.导入一个virtual table ,用来存储它所声明的每一个virtual functions的地址。

      2.在每一个class object中导入一个vptr,提供执行期的链接,使每一个object能够找到相应的virtual table。

      3.加强constructor,使它能够为vptr设定初始值,让它指向class 所对应的virtual table 。

      4.加强destructor,使它能够消抹“指向class 相关virtual table”的vptr。

      3.3 多重继承

      对于一个多重派生对象,将其地址指定给“最左端(第一个)base class的指针”,情况和单一继承时相同,因为二者都指向了相同的起始地址,至于第二个或后面的base class 的地址指定操作,则需要将地址修改过:加上(或减去,如果是downcast)介于中间的base class subobject(s)的大小。
      如果要存取第二个(或后面)的base class 中的一个data member ,不需要付出额外的成本,因为members的位置在编译时就固定了,因此存取member只是一个简单的offset的运算。
      注意,多继承的情况下,drived clas可能会有两个或两个以上虚函数表指针 。

      

      

      我们可以看到: 

    1. 每个父类都有自己的虚表。 
    2. 子类的成员函数被放到了第一个父类的表中。 
    3. 内存布局中,其父类布局依次按声明顺序排列。 
    4. 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

      3.4 虚拟继承

      下图可以表现Vertex3d 的继承体系图。左为多重继承,右为虚拟多重继承。

      

      各个class的定义如下:

    class Point2d{
    ...
    protect:
      float _x, _y;
    };
    
    class Vertex: public virtual Point2d{
    ...
    protected:
      Vertex *next;
    };
    
    class Point3d: public virtual Point2d{
    ...
    protected:
      float _z;
    };
    
    class Vertex3d: public Vertex, public Point3d{
    ...
    protected:
      float mumble;
    };

      不论是 Vertex 还是 Point3d 都内含一个 Point2d 。然而在 Vertex3d 的对象布局中,我们只需要单一一份 Point2d 就好。如何使多重继承,那么Vertex3d对象中将有两个Point2d,那么对Point2d的引用可能会有歧义。所以引入虚拟继承。然而编译器要实现虚拟继承,实在是困难度颇高。虚拟继承的原则就是:让 Vertex 和 Point3d 各自维护的Point2d 折叠成一个有Vertex3d维护的单一Point2d,并且还可以保存base class 和derived class的指针之间的多台指定操作。

      如果一个class含有virtual base class subobjects, 那么,该对象将被分割为两部分:一个不变局部和一个共享局部。不变局部中的数据,不管后继如何演化,总是拥有固定的offset,所以这部分数据可以直接存取。至于共享局部(即virtual base class),这一部分的数据,其位置会因为每次的派生操作而有变化,所以他们只能被间接存取

      如何存取class的共享局部呢?cfront编译器会在每一个derived class中安插一个指向virtual base class的指针,这样就可以间接存取。这样的实现模型会有下面两个主要缺点:

      1.每一个对象必须针对其每一个virtual base class 背负一个额外的指针。

      解决方法有:第一个,Microsoft编译器引入所谓的virtual base class table。每一个class object如果有一个或多个virtual base class,就会由编译器安插一个指针,指向virtual base class table。至于真正的virtual base class 指针,当然是被放在该表格中。

      请看下面的虚拟继承对象模型,如图。

      

      红框内即所谓的“共享局部”,其位置会因每次派生操作而有所变化。 虚拟破坏了base class 的对象完整型,虚拟继承会在自己类中生成一个虚函数表指针。在virtual function table 中放置virtual base class的offset(不是地址)。

      

      这个方法的好处是,巧妙的利用了虚函数表的结构,使得drived class 能够节省一个指针的大小。上图中蓝色曲线是offset。

      2.由于虚拟继承串链的加长,导致间接存取层次的增加。

      例如:如果我们有三层虚拟衍化,我就需要三次间接存取(经由三个virtual base class指针)。

      这个问题的解决方案有:拷贝所有的virtual base class 的指针到drived class中。这样就解决了存取时间的问题,虽然会有空间的开销。 

      总结:多继承,单继承都不会导致访问时间的增加,但是虚拟基类由于使用间接访问技术,会导致访问时间的增加。

  • 相关阅读:
    POJ1239
    HDU 2829 四边形不等式优化
    返回数字二进制的最高位位数o(n)
    矩阵快速幂 模板
    HDU4718 The LCIS on the Tree(LCT)
    HDU4010 Query on The Trees(LCT)
    HDU3487 Play With Chains(Splay)
    CF444C DZY Loves Colors
    HDU4836 The Query on the Tree(树状数组&&LCA)
    HDU4831&&4832&&4834
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/6753975.html
Copyright © 2011-2022 走看看