虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要(因为这样只会降低效率和占用更多的空间,实在是一无是处)。
以下面的一个例子为例:
#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是确定的,那么在编译CB、CC时还需要在编译器的语法树中生成CB::f,CC::f等标识,那么,在编译CD的时候,由于CB、CC都有一个函数f,此时,编译器将试图生成两个CD::f标识,显然这时就要报错了。(当我们不使用CD::f的时候,以上标识都不会生成,所以,如果去掉d.f()一句,程序将顺利通过编译)
要解决这个问题,有两个方法:
1、重载函数f():此时由于我们明确定义了CD::f,编译器检查到CD::f()调用时就无需再像上面一样去逐级生成CD::f标识了;
此时CD的元素结构如下:
--------
|CB(CA)|
|CC(CA)|
--------
故此时的sizeof(CD) = 8;(CB、CC各有一个元素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 CC : virtual public CA
{
};
class CD : public CB, public CC
{
};
void main()
{
CD d;
d.f();
}
此时,当编译器确定d.f()调用的具体含义时,将生成如下的CD结构:
----
|CB|
|CC|
|CA|
----
同时,在CB、CC中都分别包含了一个指向CA的vbptr(virtual base table pointer),其中记录的是从CB、CC的元素到CA的元素之间的偏移量。此时,不会生成各子类的函数f标识,除非子类重载了该函数,从而达到“共享”的目的。
也正因此,此时的sizeof(CD) = 12(两个vbptr + sizoef(int));
所有这一切都是编译期间决定的,只是编译器为了提供这样一个新的语法功能为我们多作了一些事情而已。
另注:
- 如果CB,CC中各定义一个int型变量,则sizeof(CD)就变成20(两个vbptr + 3个sizoef(int)
- 如果CA中添加一个virtual void f1(){},sizeof(CD) = 16(两个vbptr + sizoef(int)+vptr);
-
- 再添加virtual void f2(){},sizeof(CD) = 16不变。原因如下所示:带有虚函数的类,其内存布局上包含一个指向虚函数列表的指针(vptr),这跟有几个虚函数无关。
注:以上讨论限MS Visual C++编译器。
分享到:
相关推荐
继承作为面向对象编程的一种基本特征,其使用频率... 假设derived 虚继承自base类,那么derivd与base是一种“has a”的关系,即derived类有一个指向base类的vptr。(貌似有些牵强!某些编译器确实如此) 因此虚
C++用虚继承来消灭菱形结构二义性,解决俩爷爷的问题.zip
详解C++中虚继承虚函数 的要点和继承关系c++开发人员细细阅读哦
虚继承的代码说明(附注释)
虚继承.cpp
该资源为虚函数和虚继承及其结合的内存布局的测试,文中写明了有详细测试结果。
关于虚继承的代码说明
多重继承和虚继承.doc
C++ 内存布局虚继承 ---Empty virtual base classs (空虚基类).doc
本文以实例形式较为全面的讲述了C++的多重继承与虚继承,是大家深入学习C++面向对象程序设计所必须要掌握的知识点,具体内容如下: 一、多重继承 我们知道,在单继承中,派生类的对象中包含了基类部分 和 派生类...
虚继承 的概念的提出主要是为了解决C++多继承的问题,举个最简单的例子: 代码如下:class animal{ public : void op() {cout << “hello animal” ;} };class tiger : public animal { public : void tg...
C++的三大特性为:封装,继承,多态。但是在继承中,存在一些使用方面的问题需要注意,下面这篇文章主要给大家总结介绍了关于C++中菱形继承和虚继承的问题,需要的朋友可以参考借鉴,下面来一起看看吧。
2、虚继承 这个是比较不好理解的,对于虚继承,若派生类有自己的虚函数,则它本身需要有一个虚指针,指向自己的虚表 2、一个类中,虚函数本身、成员函数(包括
类成员的访问控制 类的继承与派生 单继承与多继承 派生类的构造、析构函数 类成员的标识与访问
GCC中, 无论是虚函数还是虚继承, 都需要将指针存储在虚函数表(virtual function table), 占用4个字节. 继承会继承基类的数据, 和虚函数表, 即继承基类的空间. 代码: /* * test.cpp * * Created ...
这几天翻箱底将去年买的《深度探索C++对象模型》这本NB的书拿出来看看,... 这是可能大家会觉得他们的大小都应该是0,因为他们中没有任何一个有明显的数据,只表示了继承关系。但是至少也认为class x应该是0吧,他什
vcall_offset(-8):当虚基类Base的引用或指针base实际接受的是Derive类型的对象,执行base->FunB()时候,由于FuncB()已
这篇文章主要讲解G++编译器中虚继承的对象内存分布问题,从中也引出了dynamic_cast和static_cast本质区别、虚函数表的格式等一些大部分C++程序员都似是而非的概念。本文是介绍C++的技术文章,假定读者对于C++有比较...