首先在谈到菱形虚拟继承之前先说明一下菱形继承:
图中 B C 两个类都继承了A类,而 B C 又都被 D类继承
按照继承的定义,派生类当中都包含了基类,而这时虚拟继承这种情况就会产生问题
这时就要引入菱形虚拟继承来了,但其实二义性的问题即使不使用菱形虚拟继承也是可以解决的,相反数据冗余不行:
class A{
public:
int aa = 0;
};
class B : public A{
};
class C : public A{};
class D : public B, public C{};
int main(){
D d1;
//d1.aa = 10;按照以上定义,如果运行这一句就会出现aa不明确的错误
//若是像以下这样显示指定则没有问题
d1.C::aa = 10;
d1.B::aa = 20;
return 0;
}
但是既然数据冗余的问题解决不了,我们还是需要使用菱形虚拟继承,即如下代码:
class A{
public:
int aa = 0;
};
class B : virtual public A{};
class C : virtual public A{};
class D : public B, public C{};
int main(){
D d1;
d1.aa = 10; //此时执行这一句就不会有问题
return 0;
}
当aa复制成功后的监视窗口如下
(D的对象内存模型↑)
没有采用菱形虚拟继承前,从内存模型中看可以很明显看出存在两份A的数据,自然会有数据冗余和二义性的问题
但是菱形虚拟继承从内存模型中看,B和C在虚拟继承A时所存储的不是A的数据,而是A的地址偏移量的地址,这两个指针叫虚基表指针,两个表叫虚基表。虚基表中存的就是偏移量。通过偏移量便可以找到下面的A。
流程就是:当访问到A中数据时,首先通过偏移量地址找到相对当前位置A的地址偏移量,然后通过地址偏移量再找到A的数据,这样就解决了两大问题。
这里有两个需要关注的点:
一是虚拟继承主要就是用来解决菱形继承问题,其他地方不可乱用。
二是一般不建议设计出多继承,一定不要设计出菱形继承,否则程序在复杂性和性能上会有较大问题(多继承可以认为是C++的缺陷之一,所以后来很多语言都舍弃了多继承,比方说JAVA)
====================================================================
现在再来说多继承指针漂移的问题
先来看一段代码
class A{
public:
int aa = 0;
};
class B{
public:
int bb = 0;
};
class C :public A, public B{
public:
int cc = 0;
};
int main(){
C p;
A* a1 = &p;
B* b1 = &p;
C* c1 = &p;
cout << a1 << ' ' << b1 << ' ' << c1 << endl;
if (b1 == c1){
cout << "b1 = c1" << endl;
}
return 0;
}
不了解这个问题的人,肯定会觉得a1,b1,c1三个指针的值应该是一样的,但事实上↓
这个问题就很玄乎了,赋的是同样的值,为什么b1和c1的值会不同呢?但是为什么下面的if判断却又判断他两相同呢?
首先是第一个问题,导致地址不同的原因是 在C进行继承时有一个先后顺序(按照代码的编写顺序),他会先继承A类,再继承B类,最后再实现自己的部分,这就导致了,a1指向了C对象中A的位置,b1指向了C对象中B的位置,从而导致了地址不同。
但为什么 if 判断会通过呢?这是第二个问题,当判断 ‘==’ 时,会分析两个地址是不是指向了同一个实例对象,如果是,就会做隐式的类型转换 ,然后再判等(虽然地址不同但指向的是同一个实例对象)。这就导致这样的结果。
多继承指针漂移和虚拟继承都是C++继承部分需要知道的点,最好还是加以记忆。