C++ Data Member语意

本文详细探讨了C++中Data Member的布局和存取方式,包括Static Data Members和Nonstatic Data Members。Static Data Members在程序的data segment中,而非static成员则存储在每个class object中。对于Nonstatic Data Members,存取时需要通过对象的地址加上成员的偏移位置。在继承和多态的场景下,成员的布局和存取方式有所不同,尤其是涉及到虚拟继承时,会出现共享区域和不变区域,存取操作更加复杂。

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;
};

图:对象布局
image

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会被覆盖掉。

图:覆盖图
image

3.2 加上多态

Point2d里包含虚函数,Point3d继承自Point2d。

image

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;
};

image

image

如果要存取第二个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;
};

image

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

image

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值