深度探索C++对象模型 个人总结 第三章 Data语意学

Data语意学

class X{};

class Y : public virtual X{};

class Z : public virtual X{};

class A: public Y, public Z{};

一个empty class如class X{},它有一个隐晦的1 byte,那是被编译器安插进去的一个char,使得这个class的两个objects得以在内存中配置独一无二的地址。
Y和Z的大小受到三个因素的影响:

  1. 语言本身所造成的额外负担overhead。语言支持virtual base classes时导致的额外负担反映在某种形式的指针身上,它要么指向virtual base class subobject,要么指向一个存放virtual base class subobject地址或者其偏移量offset的表格。
  2. 编译器对于特殊情况所提供的优化处理。virtual base class X 1 byte大小的subobject也出现在class Y和Z身上。传统上它被放在derived class的固定部分的尾端。某些编译器对empty virtual base提供特殊处理,将它视为derived class object最开头的一部分,它不用会任何的额外空间,也就是前面提到的1 byte。
  3. Alignment的限制。Alignment就是将数值调整到某数的整数倍,在32位计算机上,通常该数为4 bytes(32位),以使bus的运输量达到最高效率。

某些编译器会对empty virtual base class进行优化,使得一个empty virtual base class被视为derived class object最开头的一部分,也就是说它没有花费任何的额外空间。如果 virtual base class中放了一个(以上)的data member,那么两种编译器就会产生出完全相同的对象布局
nonstatic data members放置的是"个别的class object"感兴趣的数据,直接存放在每一个class object之中;static data members放置的是"整个class"感兴趣的数据,放置在程序的一个global data segment中,不管改class被产生出多少object(经由直接产生或间接派生),static data members永远只存在一份实例(甚至没有任何object实例其static data members也已存在)

3.1Data Member的绑定(The Binding of a Data Member)

member rewriting rule:一个inline 函数实体,在整个class声明未被安全看见之前,是不会被评估求值(evaluated)的,即如果一个inline函数在class声明之后立刻被定义的话,那么就还是对其评估求值(member scope resolution rulues)
但对于member function的argument list中的名称会在它们第一次遭遇时被适时地决议完成

3.2Data Member的布局

C++ Standard要求,在同一个access section中。members的排列只需符合"较晚出现的members在class object中有较高的地址",不要求一定要连续排列,所以members的边界调整可能会填补一些bytes
编译器可能还会合成一些内部使用的data members,以支持整个对象模型,如vptr

3.3 Data Member的存取

Static Data Members

每一个static data member只有一个实例,存放在程序的data segmet中,程序对于static member的使用其实都是通过该extern实例的操作。这也是C++语言中"通过指针和通过对象来存取member,结论完全相同"的唯一一种情况
对static data member取地址,由于static member并不内含在一个class object中,所以会得到一个指向其数据类型的指针而不是指向其class object的指针
由于static member都存放在程序的data segmet中,所以为了避免名称冲突,编译器提供了一个名为name-mangling,来获得一个独一无二的程序识别代码(不同编译器实现算法不尽相同,而且可以轻易被推导回原来的名称)

Nonstatic Data Members

nonstatic data members必须通过显式(explicit)的或隐式(implicit)的class object来存取它们,隐式的class object就是在member function中直接处理一个nonstatic data member(this指针表达)
对nonstatic data member进行存取操作,编译器需要把class object的起始地址加上data member的偏移位置(偏移位置在编译时期即可获知),甚至member如果属于base class subobject(派生自单一或多重继承串链)效果也是一样的,此时效率和C struct member或一个nonderived class的member是一样的
虚拟继承导入了一层间接性,当存取的nonstatic data member是一个virtual base class的member时,存取速度会稍慢

3.4"继承"与Data Member

C++继承模型中derived class object所表现出来的东西是自身的member和其base classes members的总和,但并不要求它们之间的排列顺序,虽然大部分C++编译器中base class members总是先出现,但virtual base class的除外

只要继承不要多态(Inheritance without Polymorphism)

共享同一个实例但又能继续使用"与实例类型相关"的实例:相对于虚拟继承并不会增加空间或存取时间上的额外负担,而且可以把base class中存取data member的代码局部化,表现出两个类间的紧密关系。分开使用的时候使用者又不需要知道object是否是独立的class类型或是彼此之间有继承的关系
易防的错误:

  1. 经验不足的人可能会重复设计一些相同的操作
  2. 把一个class分解为两层或多层时,有可能会为了"表现class体系之抽象化"而膨胀所需的空间 (其根本原因是C++保证出现在derived class中的base class subobject有其完整原样性)

加上多态(Adding Polymorphism)

继承拥有自然多态
弹性是面向对象程序设计的中心,但支持这样的弹性必然对空间和存取时间造成额外负担:
  导入一个和class有关的virtual table,用来存放它所声明的每一个virtual functions的地址(被声明的virtualfunctions的个数加上一个或两个slots(用以支持runtime type identification))
  在每个class object导入vptr提供执行期的链接,使每个object都能够找到相应的virtual table
  加强constructor,使它能为vptr设定初值,让它指向class所对应的virtual table。这意味着在derived class和每一个base class的constructor重新设定vptr的值
  加强destructor,使它能够抹消"指向class之相关virtual table"的vptr

把vptr放在class object的尾端,可以保留base class C struct的对象布局,因而允许在C程序代码中使用;把vptr放在class object的前端,对于"在多重继承之下,通过指向class members的指针调用virtual function"会带来一些帮助

多重继承(Multiple Inheritance)

多重继承既不像单一继承,也不容易模塑出其模型。多重继承的复杂度在于derived class和其上一个base class乃至于上上一个base class......之间的"非自然关系",问题主要发生于derived class object和其第二或后继的base class objects之间的转换加上【或减去,如果是downcast的话)介于中间的base class subobject(s)大小】,或是经由其支持的virtual function机制做转换

虚拟继承(Virtual Inheritance)

不同编译器对于实现间接存取的方法不同导致技术不同:
C++如果内含一个或多个virtual base class subobjects,将被分割为一个不变区域和一个共享区域,不变区域的数据拥有固定的offset,可以被直接存取,共享区域表现的是virtual base class subobject,会因每次的派生操作而有改变,所以只能被间接存取。为了能够存取class的共享部分,编译器会在每一个derived class object中安插一些指针,每个指针指向一个virtual base class。
缺点是

  1. 每一个对象针对一个virtual base class背负一个额外的指针,随着virtual base classes的个数有所增加
  2. 由于虚拟继承串链的家常导致间接存取层次的增加。

1的解决方法是经由拷贝操作取得所有nested virtual base class 指针,放到derived class object之中,这就解决了"固定存取时间"的问题,虽然付出了一些空间上的代价
2的解决方法有两种,一是引入virtual base class table,将真正的virtual base class指针放在表格中,而class object的指针则指向virtual base class table;二是virtual function table中放置virtual base class的offset(将virtual base class offset和virtual function entries混杂在一起)

3.5对象成员的效率(Object Member Efficiency)

如果没有把优化开关打开,就很难猜测一个程序的效率表现,因为程序代码潜在性地受到专家所谓"一种奇行怪癖......与特定编译器有关"的魔咒影响,在进行"程序代码层面的优化操作"以加速程序的运行之前,应该先确实地测试效率而不是靠着推论与常识判断
优化操作并不一定总是能够有效运行

3.6指向Data Members的指针

决定vptr是放在class的起始处或是尾端,决定class中access section的顺序

"指向Members的指针"的效率问题

虚拟继承妨碍了优化的有效性,虚拟继承带来的额外间接性会降低"把所有的处理都搬移到寄存器中执行"的优化能力

posted @ 2020-12-25 10:56  丸子球球  阅读(128)  评论(0编辑  收藏  举报