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的指针“的效率问题
虚拟继承妨碍了优化的有效性,虚拟继承带来的额外间接性会降低”把所有的处理都搬移到寄存器中执行“的优化能力