franksunny的个人技术空间
获得人生中的成功需要的专注与坚持不懈多过天才与机会。 ——C.W. Wendte

这次失业之后,突然发现现在工作好像真的不是很好找,没办法,主动权不在自己手里,静下心来想想就当通过笔试来给自己查漏补缺吧,昨天笔试遇到一个虚拟继承的概念,这不虽说2分的题,但是这个玩意有大内容,我学习了下,也就先整个入门出来吧:

 

为什么要引入虚拟继承?

虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,也并不常用,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要(因为这样只会降低效率和占用更多的空间,关于这一点,我自己还没有太多深刻的理解,有兴趣的可以看网络上白杨的作品RTTI、虚函数和虚类的开销分析及使用指导,说实话我目前还没看得很明白,高人可以指点下我)。

以下面的一个例子为例:

#include <iostream.h>

#include <memory.h>

class CA

{

    int k; //如果基类没有数据成员,则在这里多重继承编译不会出现二义性

public:

    void f() {cout << "CA::f" << endl;}

};

 

class CB : public CA

{

};

 

class CC : public CA

{

};

 

class CD : public CB, public CC

{

};

 

void main()

{

    CD d;

    d.f();

}

当编译上述代码时,我们会收到如下的错误提示:

error C2385: 'CD::f' is ambiguous

即编译器无法确定你在d.f()中要调用的函数f到底是哪一个。这里可能会让人觉得有些奇怪,命名只定义了一个CA::f,既然大家都派生自CA,那自然就是调用的CA::f,为什么还无法确定呢?

这是因为编译器在进行编译的时候,需要确定子类的函数定义,如CA::f是确定的,那么在编译CBCC时还需要在编译器的语法树中生成CB::fCC::f等标识,那么,在编译CD的时候,由于CBCC都有一个函数f,此时,编译器将试图生成这两个CD::f标识,显然这时就要报错了。(当我们不使用CD::f的时候,以上标识都不会生成,所以,如果去掉d.f()一句,程序将顺利通过编译

 

要解决这个问题,有两个方法:

1、重载函数f():此时由于我们明确定义了CD::f,编译器检查到CD::f()调用时就无需再像上面一样去逐级生成CD::f标识了;

此时CD的元素结构如下:

|CB(CA)|

|CC(CA)|

故此时的sizeof(CD) = 8;CBCC各有一个元素k

2、使用虚拟继承:虚拟继承又称作共享继承,这种共享其实也是编译期间实现的,当使用虚拟继承时,上面的程序将变成下面的形式:

#include <iostream.h>

#include <memory.h>

class CA

{

    int k;

public:

    void f() {cout << "CA::f" << endl;}

};

 

class CB : virtual public CA  //也有一种写法是class CB : public virtual CA

{                       //实际上这两种方法都可以

};

 

class CC : virtual public CA

{

};

 

class CD : public CB, public CC

{

};

 

void main()

{

    CD d;

    d.f();

}

此时,当编译器确定d.f()调用的具体含义时,将生成如下的CD结构:

|CB|

|CC|

|CA|

同时,在CBCC中都分别包含了一个指向CA的虚基类指针列表vbptrvirtual base table pointer),其中记录的是从CBCC的元素到CA的元素之间的偏移量。此时,不会生成各子类的函数f标识,除非子类重载了该函数,从而达到“共享”的目的(这里的具体内存布局,可以参看钻石型继承内存布局,在白杨的那篇文章中也有)。

也正因此,此时的sizeof(CD) = 12(两个vbptr + sizoef(int);

 

另注:

如果CBCC中各定义一个int型变量,则sizeof(CD)就变成20(两个vbptr + 3sizoef(int)

如果CA中添加一个virtual void f1(){}sizeof(CD) = 16(两个vbptr + sizoef(int)+vptr;

再添加virtual void f2(){}sizeof(CD) = 16不变。原因如下所示:带有虚函数的类,其内存布局上包含一个指向虚函数列表的指针(vptr),这跟有几个虚函数无关。

以上内容涉及到类对象内存布局问题,本人还难以做过多展开,先贴这么多,本篇文章只是考虑对于虚拟继承进行入门,至于效率、应用等未作展开。本文在网上文章基础上修改了下而得此篇,原文载于http://blog.csdn.net/billdavid/archive/2004/06/23/24317.aspx

另外关于虚继承和虚基类的讨论,博客园有篇文章《虚继承与虚基类的本质》,总结得更为详细一点。

 

 

posted on 2008-10-16 16:55 frank.sunny 阅读(13723) 评论(8)  编辑 收藏 引用 所属分类: C/C++学习和实践

FeedBack:
# re: [整理]虚拟继承入门
2008-10-16 17:46 | 浪迹天涯
学习了...不断积累!  回复  更多评论
  
# re: [整理]虚拟继承入门
2008-10-16 19:23 | giscn
最好不要出现必须用虚拟继承的情况  回复  更多评论
  
# re: [整理]虚拟继承入门
2008-10-16 22:28 | frank.sunny
@giscn
的确,这个技术虽然从OO角度来说是存在的,但是我们的确应该避免,说真的,工作了这么久也没有接触过,昨天笔试遇到了这么个概念,才想起翻出来看看,而且要搞透它涉及的OO概念需要很全面很扎实,我也就在别人基础上入入门而已  回复  更多评论
  
# re: [整理]虚拟继承入门
2008-10-17 17:49 | PDF阅读器下载
这个技术接触不多,打算自己好好看一下  回复  更多评论
  
# re: [整理]虚拟继承入门
2009-09-24 16:45 | 小人物做大事
在COM技术中需要掌握C++的这一特性,有兴趣的可以去研究一下COM编程,进一步的体会一下多重继承的应用。  回复  更多评论
  
# re: [整理]虚拟继承入门
2009-11-01 21:04 | Dim
这个确实很少用到哦~~~
在学校就更少用到了……
感觉,就目前自己的应用范围来说仅能当理论学习学习……  回复  更多评论
  
# re: [整理]虚拟继承入门
2010-08-23 09:51 | touseit
@Dim
log4cplus 好像用了不少虚拟继承的。一般涉及到菱形继承的时候就要注意了,这时就要考虑到虚拟继承了。  回复  更多评论
  
# re: [整理]虚拟继承入门
2010-09-27 11:14 | BK
??也正因此,此时的sizeof(CD) = 12(两个vbptr + sizoef(int));
这句有问题,是两个sizeof(int)+一个vbptr吧?
  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理



常用链接

留言簿(13)

随笔分类

个人其它博客

基础知识链接

最新评论

阅读排行榜

评论排行榜