春暖花开
雪化了,花开了,春天来了
posts - 149,comments - 125,trackbacks - 0
在园里转,看到好文章,摘抄过来。
链接:
http://www.cppblog.com/franksunny/archive/2008/10/16/64168.html


原文:

为什么要引入虚拟继承?

虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在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 21:48 Sandy 阅读(395) 评论(1)  编辑 收藏 引用 所属分类: C++

FeedBack:
# re: 虚拟继承入门
2008-10-16 21:58 | SpringSnow
不错,学习了。  回复  更多评论
  

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