55
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(); 
} 

我了解钻石问题,上面的一段代码没有这个问题。虚拟继承如何解决“菱形”(多重继承)歧义?

虚拟继承究竟如何解决问题?

我明白了什么: 当我说A *a = new D();,编译想知道D类型的对象可以被分配到A类型的指针,但它有它可以遵循两条路径,但不能决定通过它自己。

那么,虚拟继承如何解决问题(帮助编译器做出决定)?

回答

60

你想:(可实现与虚拟继承)

D 
/\ 
B C 
\/ 
    A 

而不是:(无虚继承会发生什么)

D 
/\ 
B C 
| | 
A A 

虚继承意味着将只有1基地A类的实例不是2.

您的类型D将有2个虚表指针(您可以在第一个图中看到它们),一个用于B,另一个用于虚拟继承ACD的对象大小增加,因为它现在存储2个指针;但现在只有一个A

因此,B::AC::A是相同的,所以不能模糊致电D。如果你不使用虚拟继承,你可以使用上面的第二个图。而对A成员的任何呼叫都变得模糊不清,你需要指定你想要的路径。

Wikipedia has another good rundown and example here

+0

V表指针是一个实现细节。在这种情况下,并非所有编译器都会引入vtable指针。 – curiousguy 2016-06-23 01:56:14

+10

我认为如果图形是垂直镜像的,它会更好看。在大多数情况下,我已经找到了这样的继承图来显示基础下的派生类。 (参见“downcast”,“upcast”) – peterh 2016-07-08 20:27:13

27
派生类的

实例“包含”基类的实例,因此它们在存储器看起来像:

class A: [A fields] 
class B: [A fields | B fields] 
class C: [A fields | C fields] 

因此,在没有虚拟继承,d类的实例如下所示:

class D: [A fields | B fields | A fields | C fields | D fields] 
      '- derived from B -' '- derived from C -' 

因此,请注意A数据的两个“副本”。虚拟继承意味着内部派生类有一个虚函数表指针组在运行时,它指向基类的数据,以使得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 
+0

vtable指针是否会在运行时设置? – Balu 2015-10-26 06:57:21

7

的问题是不路径编译器必须遵循。问题是该路径的端点:转换的结果。当谈到类型转换时,路径并不重要,只有最终的结果。

如果使用普通继承,每个路径都有自己独特的端点,这意味着转换的结果不明确,这就是问题所在。

如果使用虚拟继承,则会得到菱形层次结构:两个路径都会导致相同的端点。在这种情况下,选择路径的问题不再存在(或者更确切地说,不再重要),因为两条路径都会导致相同的结果。结果不再含糊不清 - 那才是最重要的。确切的路径不是。

+0

@Andrey:编译器如何实现继承......我的意思是我得到你的论点,我想感谢你如此清楚地解释它......但如果你可以解释(或指向一个引用)到编译器实际上如何实现继承,以及当我做虚拟继承时发生了什么变化 – Bruce 2010-04-17 17:41:46

5

其实例子应该如下:

#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; 
} 

...这样,输出会是正确的:“吃=> d”

虚继承只解决了重复爷爷! 但你仍然需要指定的方法是,以获得正确overrided方法虚拟...

-2
#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; 
} 
+2

如果您从类D中删除函数virtual void eat(){std :: cout <<"EAT=> D“;} DIAMOND问题再次通过抛出错误错误: 'D'中'virtual void A :: eat()'没有唯一的最终覆盖 – 2016-08-16 12:20:19

-1

这个问题可以通过使用虚拟关键字解决。

A 
/\ 
B C 
\/ 
    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; 
}