菱形继承的问题,是由多重继承的父类祖先是同一个父类导致的。如下面的情况:
菱形继承,会导致同名成员的二义性问题和数据冗余问题,用下面的代码来测试:
class A { public: int _a; }; // class B : public A class B : public A { public: int _b; }; // class C : public A class C : public A { public: int _c; }; class D : public B, public C { public: int _d; }; int main() { D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; return 0; }
当菱形继承不加处理的话。会导致下面的情况:同名成员的二义性问题
这种二义性问题,用下面的方式即可解决:指定类域
但是还会有下面的数据冗余问题:如下,成员 _a 占据了两份同样大空间(01和02那块内存地址) ,定义成员 d时,分别对 B和C开了一块空间,而B和C又要继承类A,所以B和C都对 _a 这个成员开了一样的空间。
当_a成员不是很大的时候,还好,但当有一群_a这样的同名成员的时候,内存开销就太大了!
虚继承解决了这个问题,它是怎么解决的呢?
用上面的 A,B,C,D类来举例子。当定义出一个D的对象 d 的时候,D中继承了成员 B和 C 的各自的成员变量_b和_c,也通过 B和 C继承了 A 的成员 _a,虚拟继承会将 _a 放在一个公共的区域,通过B和C中指针指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。
如成员 _b 前面的指针,指向的虚基表里存的 16进制的14,代表 20 个字节,这恰好是_b前面的指针起始位置距离 _a 的距离,知道这个距离后,访问的时候就能找到 _a 的位置了。
再如 _c 前面的指针,指向的虚基表里的 0c ,表示十进制的 12,这个指针的起始位置和 _a 的距离就是 12,访问的时候即可以找到了。
综上所述,在虚拟继承的时候,可以通过两个类里各自指针指向虚基表里的偏移量来计算同名成员的位置,并且内存位置是相同的,这也符合我们的认知。
虽然菱形继承有了虚拟继承来解决问题,但它的底层极其复杂,会对性能造成极大影响!
所以,虽然能解决,但尽量不要设计出菱形继承!!