虚拟inheritance如何解决“菱形”(多重inheritance)模糊性?
class A { public: void eat(){ cout<<"A";} }; class B: virtual public A { public: void eat(){ cout<<"B";} }; class C: virtual public A { public: void eat(){ cout<<"C";} }; class D: public B,C { public: void eat(){ cout<<"D";} }; int main(){ A *a = new D(); a->eat(); }
我了解钻石问题,上面的代码没有这个问题。
虚拟inheritance如何解决问题?
我的理解是:当我说A *a = new D();
,编译器想知道typesD
的对象是否可以分配给typesA
的指针,但它有两条可以遵循的path,但是不能自行决定。
那么,虚拟inheritance如何解决这个问题(帮助编译器做出决定)呢?
你想:(用虚拟inheritance来实现)
D / \ BC \ / A
而不是:(没有虚拟inheritance会发生什么)
D / \ BC | | AA
虚拟inheritance意味着只有一个基类A
实例不是2。
你的typesD
有两个vtable指针(你可以在第一个图中看到它们),一个用于B
,一个用于虚拟inheritanceA
C
D
的对象大小增加,因为它现在存储2个指针; 但现在只有一个A
所以B::A
和C::A
是相同的,所以不能有D
模棱两可的调用。 如果你不使用虚拟inheritance,你有上面的第二个图。 而任何对A成员的调用都变得模糊不清,你需要指定你想要的path。
维基百科有另一个很好的概要和例子在这里
派生类的实例“包含”基类的实例,所以它们像这样查找内存:
class A: [A fields] class B: [A fields | B fields] class C: [A fields | C fields]
因此,如果没有虚拟inheritance,D类的实例将如下所示:
class D: [A fields | B fields | A fields | C fields | D fields] '- derived from B -' '- derived from C -'
所以,请注意A数据的两个“副本”。 虚拟inheritance意味着派生类内部有一个在运行时设置的指向基类数据的vtable指针,以便B,C和D类的实例如下所示:
class B: [A fields | B fields] ^---------- pointer to A class C: [A fields | C fields] ^---------- pointer to A class D: [A fields | B fields | C fields | D fields] ^---------- pointer to B::A ^--------------------- pointer to C::A
问题不是编译器必须遵循的path 。 问题是这个path的终点 :这个结果。 当谈到types转换时,path并不重要,只有最终的结果。
如果你使用普通的inheritance,每个path都有自己独特的端点,这意味着转换的结果是不明确的,这是问题。
如果使用虚拟inheritance,则会得到一个菱形层次结构:两个path通向相同的端点。 在这种情况下,selectpath的问题不再存在(或者更确切地说,不再重要),因为两条path都会导致相同的结果。 结果不再含糊不清 – 那才是最重要的。 确切的path不是。
其实这个例子应该如下:
#include <iostream> //THE DIAMOND PROBLEM SOLVED!!! class A { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} }; class B: virtual public A { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} }; class C: virtual public A { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} }; class D: public B,C { public: virtual ~D(){ } virtual void eat(){ std::cout<<"EAT=>D";} }; int main(int argc, char ** argv){ A *a = new D(); a->eat(); delete a; }
…这样的输出将是正确的:“EAT => D”
虚拟inheritance只能解决祖父的重复! 但是你仍然需要指定方法是虚拟的,才能正确地覆盖方法…
这个问题可以通过使用Virtual关键字来解决。
A / \ BC \ / D
钻石问题的例子。
#include<stdio.h> using namespace std; class AA { public: int a; AA() { a=10; } }; class BB: virtual public AA { public: int b; BB() { b=20; } }; class CC:virtual public AA { public: int c; CC() { c=30; } }; class DD:public BB,CC { public: int d; DD() { d=40; printf("Value of A=%d\n",a); } }; int main() { DD dobj; return 0; }
#include <iostream> class A { public: virtual ~A(){ } virtual void eat(){ std::cout<<"EAT=>A";} }; class B: virtual public A { public: virtual ~B(){ } virtual void eat(){ std::cout<<"EAT=>B";} }; class C: virtual public A { public: virtual ~C(){ } virtual void eat(){ std::cout<<"EAT=>C";} }; class D: public B,C { public: virtual ~D(){ } }; int main(int argc, char ** argv){ A *a = new D(); a->eat(); delete a; }