您的位置:学习中国 推荐教程 C++C语言 正文
原作者:kuku 添加时间:2007-06-02 原文发表:2007-06-02 人气:9 来源:互联网

本文章共4956字,分4页,当前第1页,快速翻页:
 

 这次我们看看菱形结构的虚继承。虚继承的引入本就是为了解决复杂结构的继承体系问题。上一篇我们在讨论虚继承时用的是一个简单的继承结构,只是为了打个铺垫。

  我们先看看这几个类,这是一个典型的菱形继承结构。C100和C101通过虚继承共享同一个父类C041。C110则从C100和C101多重继承而来。


struct C041
{
 C041() : c_(0x01) {}
 virtual void foo() { c_ = 0x02; }
 char c_;
};
struct C100 : public virtual C041
{
 C100() : c_(0x02) {}
 char c_;
};
struct C101 : public virtual C041
{
 C101() : c_(0x03) {}
 char c_;
};
struct C110 : public C100, public C101
{
 C110() : c_(0x04) {}
 char c_;
};
  运行如下代码: PRINT_SIZE_DETAIL(C110)
  结果为: The size of C110 is 16
The detail of C110 is 28 c3 45 00 02 1c c3 45 00 03 04 18 c3 45 00 01
  我们可以象上一篇一样,画出对象的内存布局。 |C100,5 |C101,5 |C110,1 |C041,5 |
|ospt,4,11 |m,1 |ospt,4,6 |m,1 |m,1 |vtpt,4 |m1 |
  (注:为了不折行,我用了缩写。ospt代表偏移值指针、m代表成员变量、vtpt代表虚表指针。第一个数字是该区域的大小,即字节数。只有偏移值指针有第二个数字,第二个数字就是偏移值指针指向的偏移值的大小。)

  可以看到对象的内存布局中只有一个C041,即祖父类的部分只有一份,且放在最后面。这就是菱形继承。对比前面几篇的讨论,我们可以知道,如果没有用虚继承机制,那么在C041对象的内存布局中会出现两份C041部分,这也就是所谓的V型继承。相应的对象布局为:C041+C100+C041+C101 +C110。在V型继承中是不能直接从C110,即孙子类,直接转型到C041,即祖父类的。因为在对象的布局中有两份祖父类的实体,一份从C100而来,一份从C101而来。编译器在决议时会存在二义性,它不知道转型后到底用哪一份实体。虽然可以通过先转型到某一父类,然后再转型到祖父类来解决。但使用这种方法时,如果改写了祖父类的成员变量的内容, runtime 是不会同步两个祖父类实体的状态,因此可能会有语义错误。

  我们再分析一下上面的内存布局。普通继承的布局,顶层类在前面。多重继承时则按从左到右的顺序排。从C100和C101到C110的继承是普通继承,所以遵循这个原则,先是左父类再右父类,接下去是子类。而虚继承则要求将共享的父类放到整个对象布局的最后(即使虚父类没有被真正的共享也是如此,前在一篇的C020类就是这样。不知道打开优化开关后会不会有变化。)所以在上例中的祖父类也是被置于最后的。

  我们再看看对成员的访问情况。运行以下代码并查看相应的汇编代码。 C110 c110;
c110.c_ = 0x51;
c110.C100::c_ = 0x52;
c110.C101::c_ = 0x52;
c110.C041::c_ = 0x53;
c110.foo();
  对应的汇编代码为: 01 00423993 push 1
02 00423995 lea ecx,[ebp+FFFFF7F0h]
03 0042399B call 0041DE60
04 004239A0 mov byte ptr [ebp+FFFFF7FAh],51h
05 004239A7 mov byte ptr [ebp+FFFFF7F4h],52h
06 004239AE mov byte ptr [ebp+FFFFF7F9h],52h
07 004239B5 mov eax,dword ptr [ebp+FFFFF7F0h]
08 004239BB mov ecx,dword ptr [eax+4]
09 004239BE mov byte ptr [ebp+ecx+FFFFF7F4h],53h
10 004239C6 mov eax,dword ptr [ebp+FFFFF7F0h]
11 004239CC mov ecx,dword ptr [eax+4]
12 004239CF lea ecx,[ebp+ecx+FFFFF7F0h]
13 004239D6 call 0041DF32
  前3行是对象的初始化,调用了对象的构造函数。4、5、6行是对子类、左右父类的成员变量的赋值。我们可以看到是直接写的,因为这一层的继承是普通继承。第7、8、9行是对祖父类成员变量的赋值,和上篇讨论过的一样,是通过偏移值指针指向的偏移值来间接访问的。最后的4行指令是对成员函数的调用。我们可以看到调用的函数地址是直接给出的(最后一行),因为我们是通过对象来调用,即使是虚 函数调用 也不会有多态的行为。但是得到this指针的方式却是颇为间接,即第10、11、12行。因为这个函数在祖父类中定义,那么它操作的数据成员应该是祖父类的。因此编译器要调整this指针的位置。而祖父类又是被虚继承,因此要通过偏移值指针指向的偏移值来进行调整。
 

本文章更多内容1 - 2 - 3 - 4 - 下一页>>
本页地址
相关文章

C++之父Bjarne谈C++中的STL模板
C++箴言:用成员函数模板接受兼容类型
探索C++的秘密之详解extern C
C++箴言:为类型信息使用特征类
创建可移植的64位应用程序代码
设计OutLook风格的工具栏
C++程序员必需的修养
如何编写异常安全的C++代码
在c++程序中重启自己的一种方法
踏入C++中的雷区——C++内存管理详解
如何访问模板化基类中的名字
C++对象布局及多态之虚成员函数调用
理解隐式接口和编译期多态
C++程序中导出Word文档简易方法
C++程序设计从零开始之语句
对象布局及多态实现之成员函数的调用
C++箴言:谨慎使用私有继承
C++编程人员容易犯的10个C#错误
C语言 编程实例

相关评论


本文章所属分类:首页 推荐教程 C++C语言   C++C语言