C++ 对象模型 第三章 Data语意学

       

目录

3.1 数据成员的布局

3.2 数据成员的存取

3.2.1 静态数据成员

3.2.2 非静态数据成员

3.3 继承与数据成员

 3.3.1 只要继承不要多态

 3.4 指向数据成员的指针


        对于下述类和继承

class X{};
class Y:public virtual X{};
class Z:public virtual X{};
class A:public Y,public Z{};

       计算上述每个类所占内存空间如下:

        对于上述每个类的大小的讨论:一个空的class,如x,sizeof(X)==1。事实上虽然X中无任何数据,但他有一个隐晦的1byte,那是被编译器安插进去的一个char,这使得这个类的两个对象得以在内存中配置独一无二的地址。

        Y和Z的大小受到三个因素的影响:

  •    语言本身所造成的额外负担:当语言支持虚基类时,会造成一些额外负担。
  •    编译器对于特殊情况所提供的优化处理:虚基类的X子对象的1bytes大小也会出现在类Y和Z身上。
  •    Alignment(边界调整,调整至某些bytes的倍数,结果视不同机器而定)的限制:大部分机器上,群聚的结构体大小会受到alignmeny的限制,使他们能够更有效率地在内存中被存取。

        x和y和z的对象布局

         

        接下来计算A的大小:正常下,A应该是16bytes,但确是12bytes

        一个虚基类子对象只会在派生类中存在一份实体,不管他在类的继承体系中出现了多少此,类A的大小由如下几点决定

  •   被大家共享的唯一一个X实体,大小为1byte。
  •   Y的大小,减去因虚基类X而配置的大小,结果是4bytes,Z同理,加起来后为8bytes
  •   class A自己大小:0byte
  •   A的alignment数量:前三项和为9bytes,需要调整至4bytes边界,所以需补充3bytes,结果为12bytes。

3.1 数据成员的布局

        下述数据成员

class Point3d
{
 public:
    //
private:
    float x;
    static List<Point3d*> *freeList;
    float y;
    static const int chunkSize=250;
    float z;
};

        非静态成员数据在类对象中的排列顺序和其被声明的顺序一样,任何中间介入的静态成员数据都不会被放进对象布局之中,上述Point3d对象是由三个float组成的,次序是x,y,z。静态成员数据存放在程序的data segment中,和个别的类对象无关。

        C++规定,在同一个access section(即private,public,protected等区段)中,成员的排列只需符合”较晚出现的成员在类对象中有较高地址“这一条件即可,不一定需要连续排列。

        编译器还会合成一些内部使用的数据成员,以支持整个整个对象模型,vptr就是一个例子,传统上放在所有明确声明的成员的最后,不过也有编译器将其放在类对象的最前端

        C++标准也允许编译器将多个access sections之中的数据成员自由排列,不必在乎他们出现在类声明中的次序。例如下述声明:

class Point3d
{
public:
    //
private:
    float x;
    static List<Point3d*> *freeList;
private:
    float y;
    static const int chunkSize=250;
private:
    float z;
};

            其类对象的大小和组成和前面声明的相同,但是成员排序次序由编译器决定。

3.2 数据成员的存取

3.2.1 静态数据成员

        静态数据成员,被编译器提出与类之外,每一个成员的存取许可,以及与类的关联,并不会

导致任何空间上或执行时间上的额外负担,以及与类的关联,并不会导致任何空间上或执行时间上的额外负担。

        每一个静态数据成员只有一个实体,存放在程序的data segment之中,每次程序参阅静态成员,就会被内部转化为对该唯一的extern实体的直接参考操作。例如,Point3d origin

        orgin.chunkSize==250;

        经由成员选择运算符(即.运算符)对每一个静态成员进行存取,只是语法上的一种便宜 行事而已,成员并不存在类对象之中,因此存取静态成员并不需要通过类对象。

        就算chunkSize是从继承关系中得到的,其存取路径还是如此唯一。

3.2.2 非静态数据成员

        非静态数据成员直接存放在每一个类对象之中,除非经由明确的explict或暗喻的implicit的类对象,没有办法直接存取他们。例如下述代码

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

        对非静态数据成员进行存取操作,编译器需要吧类对象的起始地址加上数据成员的偏移量。例:

origin._y=0.0;

那么地址&orgin._y将等于

origin+(&Point3d::_y-1)

        每一个非静态数据成员的偏移量在编译期间即可获知,甚至如果成员属于一个基类子对象也是一样,因此,存取一个非静态数据成员,其效率和存取一个C struct menmber或一个非派生类的成员是一样的。

        例

Point3d origin,*pt=&orgin;
origin.x=0.0
pt->x=0.0;

        从origin存取和从pt存取,存在差异。当Point3d是一个从派生类,而在其继承结构中有一个虚基类,并且被存取的成员(即x)是一个从该虚基类继承而来的成员时,就会存在重大差异。这时候无法说pt必然指向哪一种类类型(因为不知道编译器期时这个member真正的偏移位置),所以这个存取操作必须延迟到执行期,经由一个额外的间接引导才能解决,但如果使用origin,就会不存在上述问题,其类型无疑是Point3d类,而即使他继承自虚基类,成员的偏移位置在编译期就固定了,编译器便可以经由origin解决对x的存取。

3.3 继承与数据成员

        在C++继承模型中,派生类的东西是其自身成员加上基类成员的综合。至于继承类成员和基类成员的排序次序没有强制规定,大部分编译器中,基类成员总是先出现,但属于虚基类的除外。

 3.3.1 只要继承不要多态

        例如,有下述继承关系类

         其对象布局如下:

        

         我们声明一组指针

Concrete2 *pc2;
Concrete1 *pc1_1,*pc1_2;

        若执行下述操作:*pc1_2=*pc1_1,将会逐个进行成员复制。

        然而,如果c++把派生类成员和Concrete1的子对象绑定在一起,去除填补空间,则下述操作则会存在问题:pc_1=pc2。图解如下

 3.4 指向数据成员的指针

        考虑下述类

class Point3d
{
public:
    virtual ~Point3d();
protected:
    static Point3d origin;
    float x,y,z;
};

        每一个point3d含有x,y,z和一个vptr,静态数据成员origin被放在类对象之外。根据编译器的不同,vptr所在位置不同,可能为起始处或尾端。

        取某个成员地址: &Point3d::z,将得到z在类对象中的偏移量,最低限度其值为x和y的大小总和,因为c++要求同一个access level的成员排序次序和声明次序相同。

        在一个32位机器上,每一个float是4bytes,所以,如果vptr在尾端,则三个坐标值的偏移量分别为0,4,8,如果vptr在对象的头部,则三个坐标值的偏移量分别为4,8,12.然而最终结果却总是多1,为1,5,9或5,9,13.原因如下。

float Point3d::*p1=0;
float Point3d::*p2=&Point3d::x;
//Point3d::*代表指向Point3d的数据成员的指针类型

        为了区分p1和p2,每一个真正指向成员的偏移量都被加上1,因此,在真正使用该值以指出每一个成员之前,需要先减掉1.

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一些可以作为C++对象模型习的优秀免费资源: 1. C++ Primer Plus(第六版):虽然这本书不是免费的,但你可以在一些在线图书馆或网站上找到电子版的免费样章,以及作者提供的一些免费资源,包括练习题和代码示例。 2. C++ Reference(cppreference.com):这是一个广泛的C++在线参考资料,提供了对C++语言特性、标准库和对象模型的详细解释和示例代码。它是一个非常好的资源,无论是初者还是有经验的开发者都可以从中受益。 3. C++ Object Model and Memory Management by Stanford University(斯坦福大):这是斯坦福大的一门在线课程,涵盖了C++对象模型和内存管理的基础知识。课程提供了视频讲座、幻灯片和练习题,可以帮助你深入理解C++对象模型的概念和原则。 4. C++ Object Model and Memory Management by University of Illinois(伊利诺伊大):这是伊利诺伊大的一门在线课程,专注于C++对象模型和内存管理。课程提供了讲座视频、幻灯片和编程作业,可以帮助你加深对C++对象模型的理解,并提高你的编程技巧。 5. C++ Object Model Explained by Eli Bendersky:这是一篇由Eli Bendersky撰写的文章,详细解释了C++对象模型的工作原理和实现细节。文章包含了大量的图表和示例代码,非常适合希望深入了解C++对象模型的开发者阅读。 这些资源提供了广泛而丰富的信息,可以帮助你习和理解C++对象模型的概念和原则。记住,在习过程中,实践是非常重要的,尝试编写一些涉及对象模型的代码,以加深你对该主题的理解和熟悉度。祝你在C++对象模型时取得成功!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值