虚拟继承
例1
class a
{};
class b:public a
{};
class c:public a
{};
class d:public b,c
{};
d x;
这时x会有a的两份拷贝,浪费空间,也存在二义性
此时b,c要这样声明:
class b:virtual public a
{};
class c:virtual public b
{};
代价就是不能用基类对象的指针指向虚拟继承类的对象.
{};
class b:public a
{};
class c:public a
{};
class d:public b,c
{};
d x;
这时x会有a的两份拷贝,浪费空间,也存在二义性
此时b,c要这样声明:
class b:virtual public a
{};
class c:virtual public b
{};
代价就是不能用基类对象的指针指向虚拟继承类的对象.
例2
#include <iostream.h>
#include <memory.h>
class CA
{
int k;
public:
void f() {cout << "CA::f" << endl;}
};
{
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;
CB d1;
d.f();
d1.f();
cout<<sizeof(d)<<" "<<sizeof(d1)<<endl;
}
CB d1;
d.f();
d1.f();
cout<<sizeof(d)<<" "<<sizeof(d1)<<endl;
}
结果
CA::f
CA::f
12 8
此时,当编译器确定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|
|CA|
----
同时,在CB、CC中都分别包含了一个指向CA的vbptr(virtual base table pointer),其中记录的是从CB、CC的元素到CA的元素之间的偏移量。此时,不会生成各子类的函数f标识,除非子类重载了该函数,从而达到“共享”的目的。
也正因此,此时的sizeof(CD) = 12(两个vbptr + sizoef(int));
所有这一切都是编译期间决定的,只是编译器为了提供这样一个新的语法功能为我们多作了一些事情而已。
如果CA中定义个int aa;则输出16 12;如果CB,CC中个定义一个int变量,则输出24 16
小注:
如若CA中添加一个virtual void f1(){},sizeof(CD) = 16(两个vbptr + sizoef(int)+vptr);
再添加virtual void f2(){},sizeof(CD) = 16不变。原因如下所示。
虚函数的类的程度:
不带虚函数的普通类,对象的长度恰好就是所期望的。而带有单个虚函数的对象的长度是普通变量的长度加上一个v o i d指针的长度。它反映出,如果有一个或多个虚函数,编译器在这个结构中插入一个指针( V P T R)。类中有几个虚函数长度之间没有区别,这是因为V P T R指向一个存放地址的表,只需要一个指针,因为所有虚函数地址都包含在这个表中。
在基类中加入最少一个纯虚函数(pure virtual function),可以使基类成为抽象(abstract)类,纯虚函数使用关键字virtual,并在其后
面
加上=0。
纯虚函数防止产生VTABLE,但这并不意味着我们不希望对其他函数产生函数体。我们常常希望调用一个函数的基类版本,即便它是虚拟的。把公共
代码放在尽可能靠近我们的类层次根的地方,这是很好的想法。这不仅节省了代码空间,而且能允许使改变的传播变得容易