深度探索C++对象模型--继承与Data Member
2014-08-08 01:28
459 查看
在C++继承模型中,一个继承类所表现出的东西,是自己的member与其加上基类成员的总和,而且排列顺序一般是基类在上(虚基类除外)。
假设Point2d, Point3d无继承关系,数据分布如下图:
以下分四种关系:单一继承不含virtual, 单一继承含有virtual, 多重继承,虚拟继承,分析每种的数据分布。
1 只要继承不含virtual(单一继承):这种继承并不会增加空间或存取时间上的额外负担。如果Point2d共有继承自Point3d,数据分布如下:
如上图,Point3d包含了一个Point2d子对象,C++保证出现在继承类中的基类子对象保持其原有的完整性,是为了保证指针赋值之间表现正确,如果为了节省空间,不保持原样,因为有padding等等,所以指针赋值可能会出现错误。覆盖掉原来的值,但还有多余的值。
2 加上多态:每个对象都会多一个vptr,指向虚函数表。
假如Point2d含有虚函数,为了支持多态,在这种情况下会带来空间与存取时间上的额外负担,如下:
(1)导入一个和Point2d有关的virtual table,用来存放所声明的每一个虚函数地址,元素个数一般是声明的虚函数个数,加上为了支持RTTI的一个或两个slots
(2)在每个类对象中导入一个vptr,提供执行期链接,使其找到virtual table
(3)加强constructor,使它设定vptr初值,而且可能意味着在derived class和每一个base class的构造函数中都重新设定vptr的值
(4)加强destructor,即取消vptr的值。
关于vptr的位置,一般是放在最上或最下,virtual c++放在最上面,这个在通过类对象的指针调用虚函数时会带来帮助,但是丧失了与C的兼容性。一种是尾端,如gcc,这保持了兼容,但对虚函数调用可能要一些额外操作。下图是vptr放在对象最下面 加入多态后的数据分布如下:
3 多重继承。问题主要发生在derived class objects和其第二个及其后的基类对象之间的转换。
上面的转换因为是单一继承,转换是直接的。单一继承提供了自然多态,即base type与derived type之间的类型转换是直接的,因为两者的对象都是从相同的地址开始的,差异只是derived对象较大。这个赋值操作只需执行指针赋值,就可以完全正确,且有最佳执行效率。(这种的前提是如果有vptr,vptr放在对象尾部)
对于一个多重继承,问题发生在derived class objects和其第二个及其后的基类对象之间的转换。
如果将多重派生对象地址赋给第一个基类,因为地址相同,所以与单一继承时相同,因为二者有相同的起始地址。
如果将多重派生对象地址赋给第二个或其后继子类,则需要将这个派生对象的地址修改,加上(或减去,如果是downcast)的介于中间的base class subobject(s)的大小。
如果有下面的继承关系:
对象布局如下(vptr在下):
对于上面这些类之间的赋值的操作如下:
引用不需要对0做防卫,因为引用不可能参考“无物”。
4 虚拟继承:共享的基类在每个类中只含有一份。
如下图关系:
要解决的问题:Vertex, Point3d都含有一份Point2d,而Vertex3d需要将这两个子对像中的Point2d子对象合成为一份,并且还要保持基类与继承类的指针之间多态的赋值。
一般实现方法:首先如果类含有一个或多个虚基类,先分割成两部分,一个不变区域和一个共享区域。不变区域中的数据,无论后继如何变化,总有固定的offset(从object的开头算起),所以这部分数据可以直接存取。共享区域,就是虚基类子对像,位置会因为每次派生操作发生变化,所以只能间接存取。所以编译器的差异在于间接存取的方式。
目前有两种实现模型:
(1)在每个derived class object中安插一些指针,每个指针指向virtual base class.所以存取虚基类对象,可以通过指针完成。这种模型的图如下:
在这种模型下,一个继承类与基类之间的转化:
(1)每个对象必须针对每个virtual base class背负一个额外的指针
(2)由于虚拟串链的加长,导致间接存取层次的增加
对于第一个问题,Microsoft的编译器引用了一个virtual class class table,即类似与虚函数表,编译器安插一个指针指向该表,表中的slot指向真正的虚基类地址。
第二个解决方法,是在virtual function table中放置virtual base class的offset(不是地址),即将虚函数偏移地址与虚函数表混在一起。Sun编译器中,virtual function table可经由正值或负值来索引,正表示虚函数,父表示虚基类。该模型如下图:
在该模型下,指针转化如下:
以上是发生在经由指针来存取一个继承来的虚基来的成员,如果是经过对象存取,可以直接优化为一个直接存取操作,以上问题也不会存在。
总结呀:virtual base class最有效的一种运用形式是:如果一个抽象的virtual base class,最好没有任何data成员。。。。
PS:
明天要起不来了,怎么办??
假设Point2d, Point3d无继承关系,数据分布如下图:
以下分四种关系:单一继承不含virtual, 单一继承含有virtual, 多重继承,虚拟继承,分析每种的数据分布。
1 只要继承不含virtual(单一继承):这种继承并不会增加空间或存取时间上的额外负担。如果Point2d共有继承自Point3d,数据分布如下:
如上图,Point3d包含了一个Point2d子对象,C++保证出现在继承类中的基类子对象保持其原有的完整性,是为了保证指针赋值之间表现正确,如果为了节省空间,不保持原样,因为有padding等等,所以指针赋值可能会出现错误。覆盖掉原来的值,但还有多余的值。
2 加上多态:每个对象都会多一个vptr,指向虚函数表。
假如Point2d含有虚函数,为了支持多态,在这种情况下会带来空间与存取时间上的额外负担,如下:
(1)导入一个和Point2d有关的virtual table,用来存放所声明的每一个虚函数地址,元素个数一般是声明的虚函数个数,加上为了支持RTTI的一个或两个slots
(2)在每个类对象中导入一个vptr,提供执行期链接,使其找到virtual table
(3)加强constructor,使它设定vptr初值,而且可能意味着在derived class和每一个base class的构造函数中都重新设定vptr的值
(4)加强destructor,即取消vptr的值。
关于vptr的位置,一般是放在最上或最下,virtual c++放在最上面,这个在通过类对象的指针调用虚函数时会带来帮助,但是丧失了与C的兼容性。一种是尾端,如gcc,这保持了兼容,但对虚函数调用可能要一些额外操作。下图是vptr放在对象最下面 加入多态后的数据分布如下:
3 多重继承。问题主要发生在derived class objects和其第二个及其后的基类对象之间的转换。
Point3d p3d; Point2d *p = &p3d;
上面的转换因为是单一继承,转换是直接的。单一继承提供了自然多态,即base type与derived type之间的类型转换是直接的,因为两者的对象都是从相同的地址开始的,差异只是derived对象较大。这个赋值操作只需执行指针赋值,就可以完全正确,且有最佳执行效率。(这种的前提是如果有vptr,vptr放在对象尾部)
对于一个多重继承,问题发生在derived class objects和其第二个及其后的基类对象之间的转换。
如果将多重派生对象地址赋给第一个基类,因为地址相同,所以与单一继承时相同,因为二者有相同的起始地址。
如果将多重派生对象地址赋给第二个或其后继子类,则需要将这个派生对象的地址修改,加上(或减去,如果是downcast)的介于中间的base class subobject(s)的大小。
如果有下面的继承关系:
对象布局如下(vptr在下):
对于上面这些类之间的赋值的操作如下:
Vertex3d v3d; Vertex *pv; Point2d *p2d; Point3d *p3d; pv = &v3d; //转换可能如下: pv = (Vertex*)(((char*)&v3d)+sizeof(Point3d)); //而下面的操作,只需要简单拷贝: p2d = &v3d; p3d = &v3d; //如下两个指针,可能要进行判断 Vertex3d *pv3d; Vertex *pv; pv = pv3d; //转换如下,防止pv3d为0 pv = pv3d ? (Vertex*)((char*)pv3d+sizeof(Point3d)) : 0;
引用不需要对0做防卫,因为引用不可能参考“无物”。
4 虚拟继承:共享的基类在每个类中只含有一份。
如下图关系:
要解决的问题:Vertex, Point3d都含有一份Point2d,而Vertex3d需要将这两个子对像中的Point2d子对象合成为一份,并且还要保持基类与继承类的指针之间多态的赋值。
一般实现方法:首先如果类含有一个或多个虚基类,先分割成两部分,一个不变区域和一个共享区域。不变区域中的数据,无论后继如何变化,总有固定的offset(从object的开头算起),所以这部分数据可以直接存取。共享区域,就是虚基类子对像,位置会因为每次派生操作发生变化,所以只能间接存取。所以编译器的差异在于间接存取的方式。
目前有两种实现模型:
(1)在每个derived class object中安插一些指针,每个指针指向virtual base class.所以存取虚基类对象,可以通过指针完成。这种模型的图如下:
在这种模型下,一个继承类与基类之间的转化:
Point2d *p2d = pv3d; //虚拟c++代码,转换为以下形式 Point2d *p2d = pv3d ? pv3d->vbcPoint2d : 0;这个模型有主要有以下两个缺点:
(1)每个对象必须针对每个virtual base class背负一个额外的指针
(2)由于虚拟串链的加长,导致间接存取层次的增加
对于第一个问题,Microsoft的编译器引用了一个virtual class class table,即类似与虚函数表,编译器安插一个指针指向该表,表中的slot指向真正的虚基类地址。
第二个解决方法,是在virtual function table中放置virtual base class的offset(不是地址),即将虚函数偏移地址与虚函数表混在一起。Sun编译器中,virtual function table可经由正值或负值来索引,正表示虚函数,父表示虚基类。该模型如下图:
在该模型下,指针转化如下:
Point2d *p2d = pv3d; //虚拟c++代码,转换为以下形式 Point2d *p2d = pv3d ? pv3d+pv3d->__vptr__Point3d[-1] : 0;
以上是发生在经由指针来存取一个继承来的虚基来的成员,如果是经过对象存取,可以直接优化为一个直接存取操作,以上问题也不会存在。
总结呀:virtual base class最有效的一种运用形式是:如果一个抽象的virtual base class,最好没有任何data成员。。。。
PS:
明天要起不来了,怎么办??
</pre></p><p> </p><p> </p><p> </p><p> <pre name="code" class="cpp">
相关文章推荐
- 深度探索c++对象模型之虚继承的对象构造
- 深度探索C++对象模型———Data Member的绑定
- 深度探索C++对象模型———Data Member的布局
- 深度探索C++对象模型——Data Member的布局(2)书上的错误
- 深度探索C++对象模型之:成员函数语义学--静态成员函数
- 深度探索C++对象模型之:理解虚函数机制
- 深度探索C++对象模型 之 构造函数语意学
- 深度探索C++对象模型 Data语意学笔记
- 读【深度探索C++对象模型】【中】
- 深度探索C++对象模型 第二章 读书笔记
- 深度探索C++对象模型
- 深度探索C++对象模型(4)
- 深度探索C++对象模型1
- 深度探索C++对象模型学习笔记——Function语意学
- [深度探索C++对象模型](简体版)中的蛇足
- 深度探索C++对象模型 Function语意学笔记
- 深度探索c++对象模型 小结【转】
- 深度探索C++对象模型(5)
- 深度探索C++对象模型
- 深度探索C++对象模型 第二章 读书笔记