C++对象模型解析四

来源:互联网 发布:压缩空气管道计算软件 编辑:程序博客网 时间:2024/06/03 13:10

对象的数据成员相关以及存取效率

一个对象的大小

一个对象的大小主要由以下因素决定:

1.对象自身的数据成员。

2.若对象没有成员变量,编译器会给器安插1Byte的空间,以使两个具体对象的地址不同。

3.编译器对空的虚基类的特殊处理(不继承被安插的1Bytes)

4.虚函数以及虚基类的指针

5.编译器边界调整(32位机器中,对象大小调整到4的倍数)

以一个例子解释

class X{};class Y:public virtual X{};class Z:public virtual X{};class A: public Y,public Z{};
那么X的大小为1bytes, Z Y的大小为8bytes(如果对1bytes优化处理是4bytes),A的大小为12Bytes(如果对1bytes特殊处理是8bytes)

数据成员的绑定

考虑以下代码:

extern float  x;class Point3d{public:Point3D(float,float,float);float X() const {return x;};private:float x,y,z;};

这里x不会被解析成外部的全局x,因为对函数定义的解析一直到类定义完才开始。

对于成员函数的参数列表就不会这么做了,如以下:

typedef in length;class Point3d{public:Point3D(float,float,foat);void setX(length x){_x=x;}private:typedef float length;length val;};

这时候setX的参数length就会被解析成int 而不是float了,多以我们应该吧typedef放在类的一开始。

 数据成员的存取

首先提出这个个问题:

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

的存取效率是否一致。

1.如果是静态成员变量,那么存取效率一致(.以及->只是文法上的不同而已了)通常都会被转换为Point3d::x.所以没有任何区别。

静态成员变量存储在数据段,如果对静态成员变量取地址,得到的是真实的内存地址:

&Point3d::chunkSize;

实际会被转换成const int*


2.如果是非静态成员变量

如果是对一个非静态成员的存取,origin._y=0.0    &origin._y就会被转换成:

&origin+(&Point3d::_y-1);其中&Point3d::_y是偏移值,这个值在编译期就决定了。存取效率和c的结构体是一样的


对于虚基类的成员通过指针的存取就有一点效率的损失,因为我们无法再编译期确定指针究竟指向的是派生类还是基类,所以也就无法在编译器求出其偏移值。

那么我们就理解了其实大部分情况通过指针存取数据成员和通过对象存取数据成员的效率是一致的,唯一的不一致发生在虚基类的成员变量的存取上。

继承与数据成员

在继承模型中,一般编译器把base class members放在前面(当然不包括虚基类)

下面从单一继承不含虚函数 单一继承含虚函数 多重继承 虚拟继承四种情况来剖析对象模型

1.单一继承不含虚函数的情况:

基本布局如图所示:



这种继承的请款要避免可能的内存空间的膨胀:

比如下面一个类:

class Concrete{public:private:int val;char c1;char c2;char c3;}
对应的内存模型如下:对应的内存空间是8bytes

如果改成三重继承那么内存空间的变化:

内存空间变成了16bytes。注意这种继承可能带来的内存膨胀。

2.如果一个继承关系中有了虚函数

那么就将引起虚指针,虚表,以及构造函数以及析构函数对虚指针续表的处理,这里有一个决策是,虚指针现在一般被放在了对象的头部,这样可以在多重继承下调用虚函数带来一定效率的提升,当然损失与c struct的兼容性。

从派生类到基类的指针或引用的转化,不需要指针的调整。但是如果基类没有虚函数而派生类有虚函数那么我们的自然多态就会被打破。


多种继承下的内存模型,那么派生类和某些基类之间的转换就是不自然的了,带来效率的损失。


3.虚拟继承的主流实现
以指针指向虚基类的部分。