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 유형은 두 번째 vtable 포인터 (첫 번째 다이어그램에서 볼 수 있음), BC 중 하나는 실질적으로 A을 상속합니다. D의 오브젝트 크기가 2 포인터를 저장하기 때문에 증가합니다. 그러나 지금은 A입니다.

그래서 B::AC::A은 동일하므로 D에서 모호한 호출이 있어서는 안됩니다. 가상 상속을 사용하지 않으면 위의 두 번째 다이어그램을 얻게됩니다. 그러면 A의 구성원에 대한 모든 호출이 모호 해지고 수행 할 경로를 지정해야합니다.

Wikipedia has another good rundown and example here

+0

VTABLE 포인터 구현의 세부 사항이다. 이 경우 모든 컴파일러가 vtable 포인터를 도입하지는 않습니다. – curiousguy

+10

그래프가 수직으로 대칭면 더 좋을 것이라고 생각합니다. 대부분의 경우 나는 밑에 파생 된 클래스를 보여주는 그러한 상속 다이어그램을 발견했습니다. ("downcast", "upcast"참조) – peterh

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 -' 

따라서 두 개의 "데이터"복사본을 확인하십시오.

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

7

문제 경로가 아닌 : 가상 상속 B의 경우는, C 및 D 클래스처럼되도록 기본 클래스의 데이터를 가리키는 런타임시 VTABLE 포인터 세트가 내부 파생 클래스 것을 의미 컴파일러가 따라야합니다. 문제는 해당 경로의 끝점입니다. 캐스트 결과입니다. 유형 변환에 관해서는 경로가 중요하지 않으며 최종 결과 만 변환됩니다.

일반적인 상속을 사용하는 경우 각 경로마다 고유 한 종점이 있습니다. 즉, 형 변환의 결과가 모호하므로 문제가됩니다.

가상 상속을 사용하는 경우 다이아몬드 모양의 계층 구조가 나타납니다. 두 경로 모두 동일한 끝점으로 연결됩니다. 이 경우 경로 선택 문제는 더 이상 존재하지 않으며 (더 정확하게는 더 이상 중요하지 않음) 두 경로 모두 동일한 결과로 이어지기 때문입니다. 그 결과는 더 이상 모호하지 않습니다 - 그것이 중요한 것입니다. 정확한 경로는 그렇지 않습니다.

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

... 출력 거 정확한 하나되는 방법 : 다음

+0

@Andrey : 컴파일러가 상속을 구현하는 방법 ... 나는 당신의 주장을 얻었고, 그것을 아주 명쾌하게 설명해 주신 것에 대해 감사 드리고 싶습니다. 그러나 설명 할 수 있다면 (또는 참조를 가리키면) 정말 도움이 될 것입니다. 컴파일러가 실제로 상속을 구현하는 방법 및 가상 상속을 수행 할 때 변경되는 사항 – Bruce

5

실제로 예이어야 "먹기 => D"

가상 상속 만의 중복을 해결 할아버지! 하지만 여전히 제대로 오버라이드 방법을 얻기 위해 가상 할 방법을 지정해야합니다 ...

-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에서 가상 void eat() {std :: cout <<"EAT=> D ";}을 제거하면 DIAMOND 문제는 오류 오류를 던져 다시 발생합니다. 'D'의 'virtual void A :: eat()'에 대한 고유 최종 재정의 없음 –

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