深度探索C++对象模型——3_Data语意学

文章详细阐述了C++中类的内存布局,包括数据成员的绑定、布局和访问,讨论了静态成员和非静态成员的差异,以及继承、虚函数对内存和效率的影响。此外,还提到了虚基类的处理和对象成员的效率问题,强调了实际编程中应注意的优化和潜在性能影响。
摘要由CSDN通过智能技术生成

一个空class有一个隐藏的1byte大小,是被编译器安插进去的一个char,使得类的object有不同的内存地址。
empty virtual base class:提供一个virtual interface,没有定义任何数据。在其派生类中只有一份实力,不管被继承了几次。

1.Data Member 的绑定

总结:把所有的data members防止class声明的开头处,以确保正确的绑定。
对于成员函数本体的分析,会在整个class的声明都出现了才开始。故一个Inline member function内的一个data member的绑定操作会在class声明完成后才发生(避免了一个全局的一个class内部的名称冲突)
对于成员函数参数列表并不为真,其会在第一次遭遇时被适当的完成(即导致不明确行为)。

2.Data Member的布局

总结:nonstatic data members 各个access section 之间优先级相同,access section 内部按顺序排列,不要求连续

  • nonstatic data members放置的是个别class 感兴趣的数据。对于继承而来的nonstatic data members也是如此。(但并未强制其间的排列顺序)
  • static data members放置的是整个calss感兴趣的数据。放置在程序的一个global data segment中,永远只存在一份实例,不会影响个别的class object的大小。
    对于nonstatic data members来说:
  • 同一个access section,members的排列只需要保证顺序与声明顺序一致即可,不要求连续。中间可能会填充一些其他东西,比如说边界调整,虚指针(一般来说位于最后,也可以放在任何位置)。
  • 多个access section之间的data members可以自由排列,不必在乎其在class中的声明顺序。一般来说编译器将access section连锁在一起,按声明顺序成为一个连续区块。(access section的声明并不影响内存大小)。

3.Data Member 的存取

members的存取许可(即access section:private,public,protected)不会导致任何空间和时间上的额外负担。

static data members

  1. 位于程序的data segment中,只有一个实例,被视为gloabl变量(在class 生命范围内)。 通过成员运算符进行操作只是文法上的简单,实际上并不在class object中,存取并不需要通过class object。
  2. 每一次对其的参考引用都转换为对该唯一extern实例的直接参考操作。
  3. 通过指针和通过一个对象存取没有区别的唯一情况
  4. 若取一个static data members的地址会得到一个指向其数据类型的指针,而非指向其class member的指针(并不包含在一个class object中)。
  5. 由于均放在global data segment中,多个class声明同名的变量时会导致名称冲突。对此编译器是暗中为每一个static data members编码。获得一个唯一的程序识别代码。

Nonstatic data members

  1. 存放在每一个class object中,除非经由显示的(explicit)或隐式的(implicit)class object,否则无法直接存取。
    如果在members function中直接处理一个nonstatic data members就会发生implicit(经由this指针完成)。
  2. 编译器通过把class object的起始地址+datamember的偏移位置(offset)来对一个nonstatic data member进行存取操作。
  3. 使用指针来对nonstatic data members进行操作的话其执行效率在:struct member,class member,单一继承,多重继承都完全相同。如果为虚基类的成员,存取速度会慢。
  4. origin.x=0.0; pt->x=0.0;当origin为派生类,且x来自于其继承结构中的虚基类的成员时,二者存在较大差异。pt的具体指向必须延迟到执行期,经由一个额外的间接引导才能解决。如果为origin不存在这些问题,其offset的值即使为虚基类的在编译器也固定了。

4."继承“与data member

单一继承不含virtual functions

具体继承(相对于虚继承)不会增加空间和存取时间上的额外负担。
在使用的时候需要注意:

  • 1.避免重复设计一些带有相同的函数,应当充分复用基类中的函数
  • 2.class多层拆分有可能会为了”表现class体系的抽象化“而膨胀所需的空间。(边界整合)
    类在继承后,原基类的存储空间和边界调整不变,本类的数据跟在边界调整之后,并不填充边界
    原因:
    在指向复制操作时,并不能区分边界和值。如果为填充模式的话,将一个基类的值赋值给一个派生类,那么派生类本身的值会被赋予边界值(不明确行为)

单一继承并含virtual functions

为了支持虚函数所带来的代价:

  • 1.导入一个虚表,用以存放声明的每一个虚函数的地址以及一个/两个slots。
  • 2.在class object 中导入一个vptr提供执行其的连接。其派生类也会有其基类的vptr。(原因与上面相同)
  • 3.加强construct,使其能够为vptr赋初值,让它指向类对应的虚表。
  • 4.加强destructor,使之能够消除指向与class相关虚表的虚指针。(destructor的销毁顺序是反向的
    vptr在class object中的位置:
  • 1.在class object的尾部,可以保留base class C struct 的对象的布局,允许在C程序代码中运行。
  • 2.在class object的首部,在多重继承下通过指向class members的指针调用virtual function带来帮助。但丧失了与C语言的兼容性。如果base class 没有虚函数但是派生类有,那么此时将派生类转换为基类需要编译器介入,调整地址。(由于首部vptr的缘故)

多重继承

多重继承的问题主要发生在dc和其第二或后继bc object之间的转换。
编译器一般按照继承的声明顺序排列他们在内存中的位置。
dc object将其赋值给:

  1. 其第一个bc的指针,情况与单一继承相同,代价仅是指针的地址指定操作而已。
  2. 第二个或后继的,需要进行地址修改,加上或减去(取决于内存栈的生长方向介于中间的bc的大小。
    dc 指针将其赋值给后继的bc指针时:需要进行条件测试(避免指针所指对象大小为零即定义了指针未指向对象,然后继续进行地址加减调整运算)。
    dc 引用不需要针对可能的0做防卫。因为引用不可能参考到无物。
    取第二个或后继的bc data member需要额外成本吗?
    不需要,因为members的位置在编译时期就固定了,存取只是简单的offset运算。

虚拟继承

class如果包含一个或多个虚基类,那么会被分割为两部分:一个不变区域和一个共享专区。

  • 不变区域:不管后继如何衍化,总有固定的offset,这一部分数据可以被直接存取。
  • 共享区域:位置会因为每次的派生操作而有变化,只可以间接存取。
    问题:如何取得class的共享区域部分?

解决:

为每一个dc object安插一些指针,每个指针指向一个虚基类,若要存取继承得来的虚基类成员可以通过相关指针间接完成。
问题:

  • 1.每个对象为其每个虚基类背负一个额外的指针。class object的负担随vbc个数变化
  • 2.虚继承串链加长,导致间接存取层次增加。存取随虚拟派生的深度而变化。

问题2:

经过拷贝直接获取所有虚基类的指针,放到drived class object中。

问题1:

解决1:引入虚基类表(其中置有指向虚基类的指针),为拥有虚基类的class object安插一个指针,指向虚基类表。
解决2:在虚表中放入虚基类的Offset.(*将虚基类offset与虚函数条目混在一起)

一般而言,虚基类的运用:一个抽象的虚基类,没有任何data members。

5.对象成员的效率

程序效率要实地测试。优化操作不一定总能有效运行。

6.指向data members的指针

C++要去同一个access level中的members的排列顺序应与其声明顺序相同,但对于vptr的位置没有限制(但不是首就是尾)。
如何区分一个“没有指向任何data members”的指针和一个指向“第一个data members”的指针?(即均未实例化之前)
(如:float Point::*p1=0; float Point::*p2=&Point::x)
解决:每一个真正的member offset值都+1.
实例化之前:取class 的data member的地址->得到它在class 中的偏移值。
实例化后:取object的data member的地址->该member在内存中的真正地址。
(static 和nonstatic意义相同)
效率问题:
通过指针指向class的data members,相当于为data members提供了另一种存取方式(别名)。通过这个指针而非真正的data members存取会导致执行时间多出一倍不止。
指针指向object 的data members然后进行data members的存取同样也会导致执行时间加倍。
要把“指向member 的指针”绑定到class object上,需要额外的把offset-1.
虚继承:会妨碍优化的有效性。因为每一层虚继承会导入一个额外层次的间接性,会降低“把所有处理都半岛寄存器中执行”的优化能力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值