首先,让我们来引入话题
1. 多重继承
考虑以下情况,
base class中对象可能会被derived class重复复制,为解决此问题,C++引入virtual inheritance(虚继承)
图为虚继承后的iostream继承体系图。
// 此处并非是iostream库的标准实现,只是为了方便理解
class ios {...};
class istream : public virtual ios {...};
class ostream : public virtual ios {...};
class iostream : public istream,public istream {...};
2.考虑编译器底层实现:
- 将原本istream和ostream各自维护的一个ios子对象, 转为由iostream维护的单一ios子对象
- 保存base class和derived class的指针之间的多态操作。
一般的实现方法:class中如果内含一个或多个virtual base class subobjects,将被分割为不变区域和共享区域。
其中,不变区域有固定的offset,共享区域则因为每次派生操作有变化。
一般的布局策略:先安排好derived class的不变部分,再建立共享部分。
cfront实现(cfront是早期的C++编译器)
在每个derived class object中存放指针,每个指针指向其virtual base class
考虑一下几个类
考虑为我们的Point3d重载+=运算符
//
void Point3d::operator(const Point3d & rhs){
_x+=rhs._x;
_y+=rhs._y;
_z+=rhs._z;
}
cfront下,内部实现为
//虚拟C++代码,vbc指的是virtual base class
_vbcPoint2d->x += rhs._vbcPoint2d->x;
_vbcPoint2d->y += rhs._vbcPoint2d->y;
_z += rhs._z;
derived class 和 base class之间的转换
Point2d * p2d = pv3d;
在cfront实现下,为
//虚拟C++代码
Point2d * p2d = pv3d? pv3d->_vbcPoint2d : 0;
注:当pv3d为nullptr时, p2d会被直接赋值为空指针。
思考这样做的缺点:
- 当一个class object 的virtual base class 个数增长时,每个class object的size会很大。
- 虚继承链的加长,会使得间接存取层次增加,从而影响效率。(设想我们要访问基类对象,而class object的指针指向了virtual base class ,而此virtual base class也有指向上层virtual base class的指针)(类似套娃?)
3.一些编译器的解决方法
问题1的解决方法:
1.Microsoft 在class object里存放指针,指向virtual base class table。
2.在virtual function table 中放置virtual base class的offset
但是转换操作
Point2d * p2d =pv3d;
变为
Point2d * p2d =pv3d ? pv3d+pv3d->_vptr_Point3d[-1] : 0;
我们发现利用虚函数指针即可完成所需转换,解决了问题1。
再回头考虑问题2,这样的模型实现之后,对于继承而来的对象做存取,依然难免会遇到存取层次的增加。
但是经由非多态class object来存取继承而来的对象
Point3d p3d;
p3d._x;
编译器就可以直接优化为存取操作,可以在编译时期被决议。(offset已经被提前预知)
virtual base class最有效的运用:抽象的virtual base class作为接口类,没有任何data members。
Multiple inheritance does have legitimate uses. One scenario involves combining public inheritance from an Interface class with
private inheritance from a class that helps with implementation.–《Effective C++》
第一次写技术博客,可能有一些错误,希望读者能指出,谢谢。