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

  1. 一个空类的大小是1 byte。这是为了让这一类的两个对象得以在内存中配置独一无二的地址。

  2. Nonstatic data member 放置的是“个别的class object”感兴趣的数据,static data members则放置的是“整个class”感兴趣的数据。

  3. C++对象模型把nonstatic data members直接放在每一个classs object之中。对于继承而来的nonstatic data members(不管是virtual 还是nonvirtual base class)也是如此。不过并没有强制定义其间的排列顺序。至于static data members,则被放置在程序的global data segment中,不会影响个别class object的大小。在程序之下,不管该class被产生出多少个objects(经由直接或间接派生),static data members永远只存在一份实例(即使该class,没有任何object实例,其static data members也已存在)。

  4. Nonstatic data members在class object中的排列顺序将和其声明的顺序一样,任何中间介入的static data member都不会被放进对象布局之中。因为静态数据成员放在data segment中,和个别的class object无关。

 

Data Member的存取

  1.如果有两个类,他们声明了同名的静态成员时,那么当它们都被放在程序的data segment时,就会导致名称冲突。编译器的解决方法是对每一个static data member编码,以获得独一无二的识别代码。

  2. Nonstatic data members直接存放在每一个class object之中。除非经由显式的或隐式class object,否则没有办法直接存取它们。只要程序员在一个member function中直接处理一个nonstatic data member。所谓的隐式就会发生。例如如下代码:

  

Point3d Point3d::translate(const Point3d &pt) {
    x += pt.x;
    y += pt.y;
    z += pt.z;
}

  表面上所看到的对于x,y,z的直接存取,事实上是经由一个”implicit class object“(由this 指针表达)完成的。事实上这个函数的参数是:

Point3d Point3d::translate(Point3d *const this, const Point3d &pt) {
    this->x += pt.x;
    this->y += pt.y;
    this->z += pt.z;
}

  欲对一个nonstaic data member进行存取操作,编译器需要把class object的起始地址加上data member的偏移位置。举个例子,如果:

origin._y = 0.0;

  那么地址&origin._y 将等于:

&origin + (&Point3d::_y - 1);

  注意其中的-1操作。指向data member的指针,其offset值总是被加上1,这样可以使编译系统区分出”一个指向data member的指针,用以指出class的第一个member“和”一个指向data member的指针,没有指出任何member“两种情况。

 

 继承与Data Member

  只要继承不要多态:

  假设我们有如下定义:

class Point2d {
public:
    Point2d(float x = 0.0, float y = 0.0): _x(x), _y(y){};
    
    float x() {return _x;}
    float y() {return _y;}

    void operator=(const Point2d& rhs) {
        _x += rhs.x();
        _y += rhs.y();
    }

 protected:
    float _x, _y;
};

class Point3d: public Point2d {
public:
    Point3d(float x = 0.0, float y = 0.0, float z = 0.0)
        : Point2d(x, y), _z(z){}
    float z(){return _z;}
    void operator+=(const Point3d& rhs) {
    Pointd2d::operator+=(rhs);
    _z += rhs.z();

protected:
    float _z;
}

 那么他们的内存布局是这样的:

  

 

  加上多态:

  如果将Point2d::operator+=()声明为虚函数,Point2d其余部分以及Point3d的定义都不变。那么对象内部就需要一个vptr来调用虚函数。内存布局变为如下所示:

  

 

  多重继承:

  我们有如下几个类:

class Point2d {
protected:
    float _x, _y;
};

class Point3d : public Point2d {
protected:
    float _z;
};
 
class Vertex {
protected:
    Vertex *next;
};

class Vertex3d: public Point3d, public Vertex{
protected:
    float mumble;
};

  它们的继承关系如下所示:

  

  则内存布局是这样的:

  

 C++标准没有规定基类自对象的排列顺序,大多数编译器按照基类声明的顺序在子类中安排它们的基类子对象。

  

  虚拟继承:

  我们定义如下几个类:

class Point2d {
protected:
    float _x, _y;
};

class Vertex: public virtual Point2d {
protected:
    Vertex *next;
};

class Point3d: public virtual Point2d {
protectedfloat _z;
};

class Vertex3d:
    public Vertex, public Point3d {
protected:
    float mumble;
};

 

  它们之间是虚拟继承关系:

  

  编译器会在每个derived class object中安插一些指针,每个指针指向一个virtual base。要存取继承得来的virtual base class members,可以通过指针间接完成。内存布局如下:

    

  注意:Vertex3d本身没有指向Point2d的指针。

  

 

posted @ 2015-07-16 22:28  vincently  阅读(307)  评论(0编辑  收藏  举报