Data Member语意
class object的大小可能比想象的要大,原因在于:
1. 由编译器自动加上的额外 data members,用以支持某些语言特性(主要是各种virtual特性)。
2. 因为alignment(边界调整)的需要。
1. Data Member的布局
class Point3d
{
public:
//...
private:
float x;
static List<Point3d*> *freeList;
float y;
static const int chunkSize = 250;
float z;
};
Nonstatic data members 在class object中的排列顺序将和其被声明的顺序一样,任何中间介入的static data members如freeList和chunkSize都不会被放进对象布局之中。
C++ Standard要求,在同一个access section(也就是private、public、protected等区段)中,members的排列只需符合“较晚出现的members 在class object中有较高的地址”这一条件即可。
各个members并不一定得连续排列。 中间可能会有边界调整。
C++ Standard也允许编译器将多个access sections之中的data members自由排列,不必在乎它们出现在class声明中的顺序。
2. Data Member的存取
Point3d origin, *pt = &origin;
2.1 Static Data Members
每一个 static data member 只有一个实例,存放在程序的 data segment 之中。
每次程序取用 static member时,就会被内部转化为对该唯一 extern 实例的直接取用操作。
//origin.chunkSize = 250;
Point3d::chunkSize = 250;
//pt->chunkSize = 250;
Point3d::chunkSize = 250;
从指令执行的观点来看,这是 C++ 语言中“通过一个指针和通过一个对象来存取 member,结论完全相同”的唯一情况。
static data member其实并不在 class object 之中,因此存取 static members 并不需要通过class object 。
2.2 Nonstatic Data Members
Nonstatic data members直接存放在每一个class object之中。除非经由显式的(explicit)或隐式的(implicit)class object,否则没有办法直接存取它们。
Point3d::translate(const Point3d &3d)
{
x += pt.x;
y += pt.y;
z += pt.z;
}
//内部转化
Point3d::translate(Point3d *const this, const Point3d &3d)
{
this.x += pt.x;
this.y += pt.y;
this.z += pt.z;
}
欲对一个 nonstatic data member进行存取操作,编译器需要把class object的起始地址加上data member的偏移位置(offset)。
对于:
Point3d *pt3d;
pt3d->_x = 0.0;
其执行效率在 _x 是一个struct member、一个class member、单一继承、多重继承的情况下都完全相同。
但如果 _x 是一个 virtual base class的member,存取速度会稍慢一点。
通过类的实例存取和通过指向类的指针存取的差异?
origin.x = 0.0;
pt->x = 0.0;
从origin存取和从pt存取有什么重大差异?
当point3d是一个 derived class ,而其继承结构中有一个 virtual base class ,并且被存取的member是一个从该virtual base class继承而来的member时,就会有重大差异。
此时不能确定pt必然指向哪一种class type,因此,我们也就不知道编译时期这个member真正的offset位置,所以这个存取操作必须延迟至执行期,经由一个额外的间接导引,才能够解决。
使用origin,就不会存在这个问题,其类型无疑是Point3d class ,而即使它继承自virtual class,members的offset位置也在编译时期就固定了。
3. 继承与 Data Member
3.1 只要继承但不要多态
base class subobject 在 derived class 中的原样性。
例:
class Concrete1
{
public:
//...
private:
int val;
char bit1;
};
class Concrete2 : public Concrete1
{
public:
//...
private:
char bit2;
};
class Concrete3 : public Concrete2
{
public:
//...
private:
char bit3;
};
图:对象布局
Concrete2中的bit2实际上却是被放在填补空间所用的3vytes之后。并没有占用原来用来填补的空间。
原因?
让我们声明以下一组指针:
Concrete2 *pc2;
Concrete1 *pc1_1, *pc1_2;
进行如下操作:
*pc1_2 = *pc1_1;
应该执行一个默认的memberwise复制操作,对象是被指之object的Concrete1那一部分。
如果derived class members和base class捆绑在一起,去除填补空间,那么下面的操作:
pc1_1 = pc2;
*pc1_2 = *pc1_1;
derived class subobject会被覆盖掉。
图:覆盖图
3.2 加上多态
Point2d里包含虚函数,Point3d继承自Point2d。
3.3 多重继承
单一继承提供了一种“自然多态”形式,是关于classes体系中的base type和derived type之间的转换。
base class和derived class的objects都是从相同的地址开始,其间差异只在于derived object比较大。
下面这样的指定操作:
Point3d p3d;
Point2d *p = &p3d;
把一个derived class object指定给 base class 的指针或 reference 。这个操作并不需要编译器去调停或修改地址。
class Point2d
{
public:
//...拥有virtual接口
protected:
float _x, _y;
};
class Point3d : public Point2d
{
public:
//...
protected:
float _z;
};
class Vertex
{
public:
//...拥有virtual接口
protected:
Vertex *next;
};
class Vertex3d : public Point3d, public Vertex
{
public:
//...
protected:
float mumble;
};
如果要存取第二个base class中的一个data member,将会是怎样的情况?
members的位置在编译时就固定了,因此存取members只是在一个简单的offset运算,就像单一继承一样简单,不管是经由一个指针、一个reference或是一个object来存取。
3.4 虚拟继承
Class如果内含一个或多个virtual base class subobjects,像istream那样,将被分割为两部分:一个不变区域和一个共享区域。
不变区域中的数据,不管后继如何衍化,总是拥有固定offset(从object的开头算起),所以这一部分数据可以被直接存取。
共享区域,所表现的就是virtual base subobject,这一部分的数据,其位置会因为每次的派生操作而有变化,所以它们只可以被间接存取。
class Point2d
{
public:
...
protected:
float _x, _y;
};
class Vertex : public virtual Point2d
{
public:
...
protected:
Vertex *next;
};
class Point3d : public virtual Point2d
{
public:
...
protected:
float _z;
};
class Vertex3d : public Vertex, public Point3d
{
public:
...
protected:
float mumble;
};
cfront 编译器会在每一个derived class object 中安插一些指针,每个指针指向一个virtual base class 。要存取继承得来的virtual base class members,可以通过相关指针间接完成。
本文详细探讨了C++中Data Member的布局和存取方式,包括Static Data Members和Nonstatic Data Members。Static Data Members在程序的data segment中,而非static成员则存储在每个class object中。对于Nonstatic Data Members,存取时需要通过对象的地址加上成员的偏移位置。在继承和多态的场景下,成员的布局和存取方式有所不同,尤其是涉及到虚拟继承时,会出现共享区域和不变区域,存取操作更加复杂。

690

被折叠的 条评论
为什么被折叠?



