【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 { protected: float _z; }; class Vertex3d: public Vertex, public Point3d { protected: float mumble; };
它们之间是虚拟继承关系:
编译器会在每个derived class object中安插一些指针,每个指针指向一个virtual base。要存取继承得来的virtual base class members,可以通过指针间接完成。内存布局如下:
注意:Vertex3d本身没有指向Point2d的指针。