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