가상 상속

Virtual inheritance
가상 상속이 해결하려는 문제인 다이아몬드 상속 다이어그램

가상 상속기본 클래스 멤버 변수의 사본 하나만 손자 파생 클래스에 상속되도록 하는 C++ 기법이다.가상 상속 없이 두 클래스인 경우B그리고C계급으로부터 물려받다.A, 그리고 클래스.D양쪽으로부터 물려받다.B그리고C그때D의 사본 2부를 포함할 것이다.A 멤버 변수: 하나의 ~를 통해B, 그리고 하나를 통해C스코프 해상도를 사용하여 독립적으로 접근할 수 있다.

대신, 만약 수업들이B그리고C사실상 계급으로부터 물려받다.A, 그리고 클래스의 사물들D클래스의 멤버 변수 집합 하나만 포함A.

이 기능은 가상 기반이 파생 클래스와 그로부터 파생된 모든 클래스의 공통 하위 객체로 만들기 때문에 다중 상속에 가장 유용하다.이는 파생계급(생성계급)의 관점에서 사용하고자 하는 조상계급에 대한 모호성을 명확히 함으로써 다이아몬드 문제를 피하는 데 사용할 수 있다.D위의 예에서) 가상 기반(기존)A)은 의 직접 베이스 클래스인 것처럼 행동한다.D, 베이스를 통해 간접적으로 파생된 클래스가 아님(B또는C).[1][2]

상속이 부품 구성보다 세트 제한을 나타낼 때 사용한다.C++에서는 계층 전체에서 공통으로 사용할 기본 클래스를 가상 클래스와 함께 가상 클래스로 표시한다.virtual 키워드

다음 클래스 계층 구조를 고려하십시오.

구조상의 애니멀 {     가상의 ~애니멀() = 체납;     가상의 공허하게 하다 먹다() {} };  구조상의 포유류: 애니멀 {     가상의 공허하게 하다 숨쉬다() {} };  구조상의 윙에디애니멀: 애니멀 {     가상의 공허하게 하다 플랩() {} };  // 박쥐는 날개가 달린 포유동물이다. 구조상의 박쥐: 포유류, 윙에디애니멀 {};  박쥐 박쥐; 

위와 같이, 에 대한 호출.bat.Eat두 개가 있기 때문에 모호하다.Animal(iii) 기본 클래스:Bat, so anythingBat물체는 두 가지 다른 것을 가지고 있다.Animal기본 클래스 하위 개체그래서 참조를 에 직접 바인딩하려는 시도Animal의 하위 목적.Bat바인딩이 본질적으로 모호하기 때문에 객체가 실패할 수 있음:

박쥐 b; 애니멀& a = b;  // 오류: 박쥐가 어떤 동물 하위 객체를 캐스팅,                 // 포유류::Animal 또는 WingedAnimal:동물? 

모호함을 해소하려면 명시적으로 변환해야 한다.bat기본 클래스 하위 개체로:

박쥐 b; 애니멀& 포유 동물 = 정적_캐스트<포유류&>(b);  애니멀& 날개가 달린 = 정적_캐스트<윙에디애니멀&>(b); 

전화를 걸기 위해서Eat, 동일한 모호성 또는 명시적 자격 부여가 필요하다.static_cast<Mammal&>(bat).Eat()또는static_cast<WingedAnimal&>(bat).Eat()또는 대안으로bat.Mammal::Eat()그리고bat.WingedAnimal::Eat()명시적 자격은 포인터와 물체 모두에 대해 더 쉽고 균일한 구문을 사용할 뿐만 아니라 정적 파견도 허용하기 때문에 논란의 여지가 없이 선호되는 방법이 될 것이다.

이 경우 의 이중 상속.Animal우리가 그 관계를 모델링하기를 원하듯이, 아마도 원하지 않을 것이다.Bat이다Animal)은 한 번만 존재함, 즉Bata이다Mammal그리고 is a이다.WingedAnimal그것이 라는 것을 의미하지 않는다.Animal두 번: aAnimal기본 클래스는 다음 계약에 해당한다.Bat구현(위의 "a" 관계는 실제로 "요구사항"을 의미한다) 및Bat오직 을 실행하다Animal한 번 계약하다'한 번뿐'이라는 현실의 의미는.Bat실행 방법은 하나뿐이어야 한다.Eat, 두 가지 다른 방법이 아니라, 그 두 가지 방법이 있다.Mammal에 대한 견해Bat먹는 것이다, 또는WingedAnimal에 대한 견해Bat. (첫 번째 코드 예에서는Eat어느 쪽에서도 오버라이드되지 않다.Mammal또는WingedAnimal그래서 두 사람은Animal하위 객체는 실제로 똑같이 행동하겠지만, 이것은 단지 퇴보한 경우일 뿐, C++ 관점과 차이가 없다.)

이러한 상황을 다이아몬드 상속(다이아몬드 문제 참조)이라고 부르기도 하는데, 그 이유는 상속도가 다이아몬드 모양이기 때문이다.가상 상속은 이 문제를 해결하는 데 도움이 될 수 있다.

해결책

우리는 다음과 같이 우리의 수업을 다시 선언할 수 있다.

구조상의 애니멀 {     가상의 ~애니멀() = 체납;     가상의 공허하게 하다 먹다() {} };  // 실질적으로 Animal을 계승하는 두 가지 등급: 구조상의 포유류: 가상의 애니멀 {     가상의 공허하게 하다 숨쉬다() {} };  구조상의 윙에디애니멀: 가상의 애니멀 {     가상의 공허하게 하다 플랩() {} };  // 박쥐는 여전히 날개가 달린 포유동물이다. 구조상의 박쥐: 포유류, 윙에디애니멀 {}; 

Animal의 일부Bat::WingedAnimal이제 똑같다 Animal의 예로서.Bat::Mammal, 즉 A라는 말이다.Bat단 하나, 공유,Animal그 대표에 있어서의 예.Bat::Eat확실하지 않다.또한, 다음에서 직접 캐스팅BatAnimal또한 명확하지 않다. 이제 오직 하나만이 존재한다.Animal어떤 경우.Bat로 변환될 수 있다.

의 단일 인스턴스를 공유하는 기능Animal사이의 친족.Mammal그리고WingedAnimal메모리 오프셋을 기록하여Mammal또는WingedAnimal기지의 구성원들과 그 기지의 사람들.Animal파생 계급 내에서그러나 이 오프셋은 일반 사례에서 런타임에만 알 수 있으므로Bat반드시 ()이 되어야 한다vpointer,Mammal,vpointer,WingedAnimal,Bat,Animal. 가상으로 상속되는 상속 계층당 하나씩, 두 개의 vtable 포인터가 있다.Animal. 이 예에서, 다음 예에서.Mammal을 위한 것WingedAnimal따라서 개체 크기는 두 개의 포인터가 증가했지만, 지금은 한 개뿐입니다.Animal모호하지 않고.모든 개체 유형Bat동일한 vpointer를 사용하지만 각각Bat사물은 그 고유한 것을 포함할 것이다.Animal반대하다다른 클래스가 다음 클래스에서 상속되는 경우Mammal예를 들어Squirrel, 그리고 vpointer in the.Mammal의 일부Squirrel일반적으로 vpointer와 다를 것이다.Mammal의 일부Bat비록 그들이 같은 것이 될 수도 있지만Squirrel클래스가 와 같다Bat.

여러 조상의 추가 예제

이 예제는 기본 클래스가 있는 경우를 보여 주기 위한 것이다.A생성자 변수 있음msg그리고 추가 조상E손주 계층에서 파생되었다.D.

A / \ B C \ / D E

여기,A둘 다 건설해야 한다.D그리고E. 또한 변수에 대한 검사msg등급이 어떻게 되는지 보여 준다.A중간 파생 클래스의 기본 클래스와 반대로, 파생 클래스의 직접 기본 클래스가 된다.A그리고 마지막 파생 수업.아래의 코드는 여기에서 대화형으로 탐색할 수 있다.

#include <끈> #include <아이오스트림>  계급의 A                     {      사유의:          찌꺼기::끈을 매다 _msg;      공중의:         A(찌꺼기::끈을 매다 x): _msg(x) {}          공허하게 하다 시험하다(){ 찌꺼기::뻐드렁니가 나다<<"안녕하십니까 A: "<<_msg <<"\n"; }  };   // B,C는 사실상 A를 상속한다. 계급의 B: 가상의 공중의 A   { 공중의: B(찌꺼기::끈을 매다 x):A("b"){}  };  계급의 C: 가상의 공중의 A   { 공중의: C(찌꺼기::끈을 매다 x):A("c"){}  };   // B,C는 사실상 A를 상속받으므로 A는 각 아동에 대해 생성되어야 한다. 계급의 D: 공중의         B,C { 공중의: D(찌꺼기::끈을 매다 x):A("d_a"),B("d_b"),C("d_c"){}  };  계급의 E: 공중의         D   { 공중의: E(찌꺼기::끈을 매다 x):A("e_a"),D("e_d"){}  };   // A을(를) 구성하지 않고 중단 // 클래스 D: 공용 B,C { 공용: D(std::string x):B(x),C(x){};  // A을(를) 구성하지 않고 중단 //클래스 E: 공개 D { 공개: E(std::string x):D(x){} };   인트로 본래의(인트로 argc, 마를 뜨다 ** 아그브){     D d("d");      d.시험하다(); // A: d_a 안녕      E e("e");      e.시험하다(); // A: e_a에서 hello } 

순수 가상 메서

기본 클래스에 순수 가상 메서드가 정의되어 있다고 가정해 보십시오.파생 클래스가 기본 클래스를 가상으로 상속하는 경우 순수 가상 방법은 해당 파생 클래스에 정의될 필요가 없다.그러나 파생 클래스가 가상으로 기본 클래스를 상속하지 않는 경우 모든 가상 방법을 정의해야 한다.아래의 코드는 여기에서 대화형으로 탐색할 수 있다.

#include <끈> #include <아이오스트림>  계급의 A                     {      보호받는:          찌꺼기::끈을 매다 _msg;      공중의:         A(찌꺼기::끈을 매다 x): _msg(x) {}          공허하게 하다 시험하다(){ 찌꺼기::뻐드렁니가 나다<<"안녕하십니까 A: "<<_msg <<"\n"; }          가상의 공허하게 하다 pure_virtual_test() = 0; };   // B,C가 사실상 A를 상속하므로 순수한 가상 방법 pure_virtual_test는 정의할 필요가 없다. 계급의 B: 가상의 공중의 A   { 공중의: B(찌꺼기::끈을 매다 x):A("b"){}  };  계급의 C: 가상의 공중의 A   { 공중의: C(찌꺼기::끈을 매다 x):A("c"){}  };   // B,C는 사실상 A를 상속받으므로 A는 각 아동에 대해 생성되어야 한다. // 단, D가 B,C를 가상으로 상속하지 않기 때문에 A *musted 가상 메서드를 정의해야 함* 계급의 D: 공중의 B,C {      공중의:          D(찌꺼기::끈을 매다 x):A("d_a"),B("d_b"),C("d_c"){}         공허하게 하다 pure_virtual_test() 무효로 하다 { 찌꺼기::뻐드렁니가 나다<<"가상 인사말 위치: "<<_msg <<"\n"; } };   // 부모가 가상 방법을 정의한 후 순수 가상 방법을 재정의할 필요는 없음 계급의 E: 공중의 D {      공중의:      E(찌꺼기::끈을 매다 x):A("e_a"),D("e_d"){}   };   인트로 본래의(인트로 argc, 마를 뜨다 ** 아그브){     D d("d");      d.시험하다();      d.pure_virtual_test();      E e("e");      e.시험하다();      e.pure_virtual_test(); } 

참조

  1. ^ Milea, Andrei. "Solving the Diamond Problem with Virtual Inheritance". Cprogramming.com. Retrieved 2010-03-08. One of the problems that arises due to multiple inheritance is the diamond problem. A classical illustration of this is given by Bjarne Stroustrup (the creator of C++) in the following example:
  2. ^ McArdell, Ralph (2004-02-14). "C++/What is virtual inheritance?". All Experts. Archived from the original on 2010-01-10. Retrieved 2010-03-08. This is something you find may be required if you are using multiple inheritance. In that case it is possible for a class to be derived from other classes which have the same base class. In such cases, without virtual inheritance, your objects will contain more than one subobject of the base type the base classes share. Whether this is what is the required effect depends on the circumstances. If it is not then you can use virtual inheritance by specifying virtual base classes for those base types for which a whole object should only contain one such base class subobject.