C++对象模型之三 数据成员笔记
首先纠正第一章中关于静态成员在对象之外,对象用指针连接,实际上对象没有用指针去连接他们,而是通过类存取的. 下面是空类的虚继承.
Class x {};
class y :public virtual x{};
lass z:public virtual x{};
class A: public y,public z {};
其大小是 X =1 Y=4 Z=4 A=8;虚继承基类 X 隐藏1字节字符使的类两个对象在内存中有不同的地址。
数据成员的布局
非静态数据成员在类对象中的排列顺序和其被声明的顺序一样,任何中间介入的静态数据成员都不会放进去。在同一个存取节中各个成员并不一定连续排列,符合较晚出现的成员在类对象中有较高的地址。编译器可能合成些内部使用的数据成员以支持整个对象模型。编译器把多个相同的存取节合并在一起。
数据成员的存取
1 静态数据成员:在对象之外,并做为全局变量看,每一个对象的存取许可及类的关联并不会导致空间和时间的负担。
Point3d origin, *pt=&origin;
Origin.chunSize=250;
Pt->chunSize=250;
被内部转化为:
Point3d::chunkSize=250; Point3d::chunkSize=250; 无论是对象还是指针都一样。
如果静态数据成员经函数调用:foobar ( ).chunkSize=250; 被转化为:(void) foobar(); Point3d::chunkSize=250;
取一个静态数据成员的地址,得到的是指向该数据类型的指针,而不是一个指向其类成员的指针。
&Point3d::chunkSize; è const int *
2 非静态数据存取
欲要对非静态数据存取,编译器需要把类对象的起始地址加上数据成员的偏移量。
Origin.y=0.9; è &Origin+(&Point3d::y-1);
虚继承比其它方式存取数据成员会比较慢点。用对象和指针存取虚继承而来的数据成员时指针存取操作必须延迟到执行期,而对象在编译期就固定了。
继承与数据成员
在C++模型中派生类对象所表现出来的东西,是自己的成员们加上其基类们的成员的总和,基类成员总是先出现,但是虚继承类除外。
1 单继承: 类Point3d单继承于Point2d

2 单继承含虚函数:Point3d单继承于Point2d,Point2d有virtual void foobar();

3 多继承:Point3d继承于Point2d; Vertex3d 多继承于 Point3d 和Vertex

4虚继承:Point3d virtual Point2d ; Vertex :virtual Point2d; Vertex3d:public Point3d,public Vertex;
1 每个对象会多一个指针指向虚继承基类 (Point2d);
2 每次继承时存取时间会增加,因为要调整一些指针;
所以有两种对象模型:一种就是增加指针继承时调整指针;另一种是把虚继承基类的偏移地址放在虚函数表第一位值。


对象成员的效率
我使用BCB5 在塞扬A466 128SDRAM 下
void __fastcall TForm1::Button1Click(TObject *Sender)
{ //单个变量
float pA_x,pB_x,pA_y,pB_y,pA_z,pB_z;
pA_x=1.725;pA_y=0.875;pA_z=0.478;pB_x=0.315;pB_y=0.317;pB_z=0.838;
long StartTime=GetTickCount();
for(int i=0;i<10000000;i++)
{
pB_x=pA_x-pB_z;
pB_y=pA_y+pB_x;
pB_z=pA_z+pB_y;
}
long EndTime=GetTickCount();
Label1->Caption=EndTime-StartTime;
}
void __fastcall TForm1::Button2Click(TObject *Sender)
{
class Point3d
{
public:
Point3d(float xx=1.725,float yy=0.838,float zz=0.315): _x(xx),_y(yy),_z(zz){}
float & x() {return _x;}
float & y() {return _y;}
float & z() {return _z;}
private:
float _x,_y,_z;
};
Point3d A,B;
long StartTime=GetTickCount();
for(int i=0; i<10000000;i++)
{
B.x()=A.x()-B.z();
B.y()=A.y()+B.x();
B.z()=A.z()+B.y();
}
long EndTime=GetTickCount();
Label2->Caption=EndTime-StartTime;
}
剩余的代码没有给出,大家自己可以做做,对象不会很慢,纳闷结构体比单个变量快?
|
未优化 |
优化 |
单个变量 |
0.530 (秒) |
0.521 (秒) |
结构体 |
0.260 (秒) |
0.200 (秒) |
数组 |
0.531 (秒) |
0.521 (秒) |
对象 |
2.083 (秒) |
0.521 (秒) |
书上还做了各种继承的测试结论:
1 关闭优化后内联函数存取比较慢,所以程序员应该实际测试下,不要光凭推理或常识判断。
2 有时侯优化操作并不一定总是有效运行。
3 虚继承在优化后同样效率令人失望。
指向数据成员的指针
class Point3d
{
public:
virtual ~Point3d();
protected:
static Point3d origin;
float x,y,z;
};
因为 vptr 占4个字节 float 占4个字节 所以 vptr放在头部三个数据成员的偏移量是 4,8,12;vptr在尾部0,4,8。
如果你取数据成员的地址 &Point3d::x 返回的总是多1个字节,为什么?
因为
float Point3d::*p1=0;
float Point3d::*p2=&Point3d::x;
if (p1==p2) //无法区分 所以不得不加上1个字节
而 &Point3d::x; 和 &origin.Z 是不同的 前者得到的是在类中的偏移量,后者得到是真正的内存地址。
指向成员的指针效率
|
未优化 |
优化 |
直接存取 |
1.42 |
0.8 |
指针指向已绑定的成员 |
3.04 |
0.8 |
指针指向已数据成员 |
5.34 |
0.8 |
指向数据成员的指针
|
未优化 |
优化 |
没有继承 |
5.34 |
0.8 |
单一继承(三层) |
5.34 |
0.8 |
虚继承(一层) |
5.44 |
1.6 |
虚继承(二层) |
5.51 |
2.14 |
虚继承它妨碍了优化,每一层中导入了额外的层次间接性
pB.*bx 转化为 &pB->__vbtr+(bx-1) 而不是 &pB+(bx-1)
作者名:曾凡坤, 又名曾牧暗鲨,网名:大白鲨 2003-7-29

|