1.类对象的大小
一个空的class如
class X{};//size of ==1
编译器安插进去一个char。这使得class 的两个objects的以在内存中拥有不同的地址
类大小主要由三个因素影响:
1.语言本身造成的负担:当拥有virtual base class时,需要一个指针的支持
2.编译器对于其优化处理:空的class插入一个字节的char
3.对齐机制:当一个类的内存大小不足四字节的倍数时,编译器将自动填充,为了寻址方便
有些编译器对空类进行了优化处理,当空类作为基类时不占用继承类的内存,当空类单独为一个对象时占用1byte,这样可能避免了对齐机制,优化了内存空间
2.数据成员的绑定
1.把所有data members放在class声明的起头处,保证正确的绑定
2.一个inline 函数实体,在整个class声明未被完全编译之前是不会被编译的
但是成员函数的形参表中的变量还是在第一次遇到时就进行编译
3.数据成员的布局
静态数据成员存储在静态存储区中,独立于任何类对象;非静态数据成员存储在类对象中
1.在类中的非静态数据成员并非一定连续排列,只要求在同一访问权限段(private、public、protected等区段)中的数据成员,较晚出现的members在class object中有较高的地址。
2.指向虚函数表的指针vptr可以放在类对象的尾部也可以放在类对象的首部
4.数据成员的存取
1.静态数据成员
每一个静态数据成员都只有一个实体,存储在data segment中,每次对此成员的存取,都会内部转化为对此唯一实体的直接操作
若取一个静态数据成员的地址,会的到一个指向其数据类型的指针,而不是指向 class member的指针
如果两个class,每一个都声明了一个相同的静态数据成员x,那么当他们被放到静态存储区时会发生名称冲突。编译器会暗中对每一个静态数据成员进行编码以区分
2.非静态数据成员
非静态数据成员必须通过指向该类类型的指针或者该类类型的对象进行存取,通过this指针和此数据成员在类对象中的offset进行存取
每一个非静态数据成员的偏移量(offset)在编译时即可获知,甚至如果一个数据成员属于base class subobject(派生自单一或者多重继承串链)也是一样。
但是虚拟继承不同,后面讲解
5.继承与数据成员
在C++继承模型中,一个派生类对象的成员是其自己的成员加上其base class成员的总和。
1.单一继承
继承类对象总是把基类对象放在自己的首部,然后再放自己的成员。因此,子类通过对象或者通过对象指针访问基类成员不会存在间接性,基类成员在编译期就可以确定其offset值(基类成员在基类中的offset值和在子类中的offset值是一样的)。因为基类对象在子类对象的首部,这样当基类指针被子类赋值时,基类指针仍然指向基类对象起始地址。
当存在多态(虚函数)时,编译器会自动根据vptr修改数据成员的vptr
2.多重继承
此种情况较上述情况麻烦,需要编译器进行地址转换。基类按照继承声明的顺序在继承类中排列,因此第一个基类的地址不需要转化,直接复制即可。而处于中间的基类的地址就需要通过this+中间类的大小才能够确定,这个工作由编译器完成。地址转化的时候首先判断子类地址是否为零。
这种情况下,在编译时就确定了非静态数据成员的offset,这样通过对象或者指向对象的指针存取对象就不会有区别
3.虚拟继承
虚拟继承无论继承多少个基类虚拟,都只存在一个虚拟基类的对象
那么虚拟基类在继承类对象的内存中只存在一个实例
现在通用的是将虚拟基类对象实例放在继承类对象的底部,在继承类成员的下面
虚拟基类对象在每个继承类中的offset值是不同的,因此如果通过对象指针存取基类对象成员,不能在编译期确定基类成员的offset值(运行时确定)。但是,通过对象存取基类成员时是在编译期确定offset值。
如何存取虚基类中的数据成员,有两种方式:
1.在每一个继承类对象中安排指向虚基类对象的指针,每一个指针指向一个虚基类对象,之后可以用相关的指针来存取虚基类成员。
2.在虚函数表中放入虚基类的offset,通过指向虚函数表的指针,获取虚基类的offset,如下图
在虚拟继承时,通过对象和指向对象的指针存取非静态数据成员有不同的效率,通过指针存取成员时,由于不知道指针指向的实际类类型,所以不能知道虚基类的实际offset,必须在运行时才能确定