接下来我将持续更新“深度解读《深度探索C++对象模型》”系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,自动获得推文和全部的文章列表。
前面两篇请通过这里查看:
深度解读《深度探索C++对象模型》之数据成员的存取效率分析(一)
深度解读《深度探索C++对象模型》之数据成员的存取效率分析(二)
这一节讲解具体继承的情况,具体继承也叫非虚继承(针对虚继承而言),分为两种情况讨论:单一继承和多重继承。
单一继承
在上面的例子中,所有的数据都封装在一个类中,但有时可能由于业务的需要,需要拆分成多个类,然后每个类之间具有继承关系,比如可能是这样的定义:
class Point {
int x;
};
class Point2d: public Point {
int y;
};
class Point3d: public Point2d {
int z;
};
对于这样的单一继承关系,在前面的文章《深度解读《深度探索C++对象模型》之C++对象的内存布局》中已经分析过了。一般而言, Point3d 类的内存布局跟独立声明的类的内存布局没什么差别,除非在某些情况下,编译器为了内存对齐而进行填充,造成空间占用上会变大的情况,但对于存取效率而言没什么影响,因为在编译期间就已经确定好了它们的偏移值。完善上面的例子,在 main 函数中定义 Point3d 的对象,然后访问各个成员,看看对应的汇编代码。
int main() {
printf("&Point2d::y = %d\n", &Point2d::y);
printf("&Point3d::y = %d\n", &Point3d::y);
Point3d p3d;
p3d.x = p3d.y = p3d.z = 1;
return 0;
}
上面两行打印代码输出的都是 4 ,再看看第5行代码对应的汇编代码:
mov dword ptr [rbp - 8], 1
mov dword ptr [rbp - 12], 1
mov dword ptr [rbp - 16], 1
生成的汇编代码跟独立类的汇编代码没有区别,这说明单一继承的存取效率跟没有继承关系的类的存取效率是一样的。
多重继承
或许业务需要,继承关系不是上面的单一继承关系,而是需要改成多重继承关系,多重继承下对象的存取效率是否会受影响?我们来看一个具体的例子:
#include <cstdio>
class Point {
public:
int x;
};
class Point2d {
public:
int y;
};
class Point3d: public Point, public Point2d {
public:
int z;
};
int main() {
printf("&Point2d::y = %d\n", &Point2d::y);
printf("&Point3d::x = %d\n", &Point3d::x);
printf("&Point3d::y = %d\n", &Point3d::y);
printf("&Point3d::z = %d\n", &Point3d::z);
Point3d p3d;
p3d.x = p3d.y = p3d.z = 1;
Point2d* p2d = &p3d;
p2d->y = 2;
return 0;
}
输出结果是:
&Point2d::y = 0
&Point3d::x = 0
&Point3d::y = 0
&Point3d::z = 8
第1、2行输出是
0
很正常,因为对于
Point2d
类来说只有一个成员
y
,也没有继承其他类,所以
y
的偏移值是
0
,第2行输出的是
x
的偏移值,它从
Point
类继承而来,排在最前面,所以偏移值也是
0
。但为什么第3行输出也是
0
?难道不应该是
4
吗?从第4行的输出看到z的偏移值是
8
,说明前面确实有两个成员在那里了。其实这里应该是编译器做了调整了,因为
Point2d
是第二基类,访问第二基类及之后的类时需要调整
this
指针,也就是将
Point3d
对象的起始地址调整为
Point2d
的起始地址,一般是将
Point3d
的地址加上前面子类的大小,如
&p3d+Sizeof(Point)
。
通过分析汇编代码,多重继承的情况,如果是通过对象来存取数据成员,是跟独立类的存取效率是一致的,如果是通过第二及之后的基类的指针来存取,则需要调整 this 指针,可以看到对应的汇编代码也多了好好多行,所以效率上会有一些损失。
如果您感兴趣这方面的内容,请在微信上搜索公众号iShare爱分享并关注,以便在内容更新时直接向您推送。