왜 또 다른 대답입니까?
글쎄요, SO에 대한 많은 게시물과 외부 기사에 따르면 다이아몬드 문제는 A
두 개 (의 각 부모에 대해 하나씩) 대신 단일 인스턴스를 생성 D
하여 모호성을 해결함으로써 해결됩니다. 그러나 이것은 프로세스에 대한 포괄적 인 이해를 제공하지 못했으며 결국 다음과 같은 더 많은 질문을 받게되었습니다.
- 만약에
B
와 C
시도는 다른 인스턴스 생성하는 A
다른 매개 변수를 매개 변수화 된 생성자를 호출 예를 ( D::D(int x, int y): C(x), B(y) {}
)? 의 A
일부가 될의 인스턴스 는 D
무엇입니까?
- 제가 아닌 가상에 대한 상속 사용하는 경우
B
에, 그러나 가상 하나 C
? A
in의 단일 인스턴스를 만드는 데 충분 D
합니까?
- 약간의 성능 비용과 다른 단점없이 가능한 다이아몬드 문제를 해결하기 때문에 지금부터 기본적으로 가상 상속을 항상 예방 조치로 사용해야합니까?
코드 샘플을 시도하지 않고 행동을 예측할 수 없다는 것은 개념을 이해하지 못한다는 것을 의미합니다. 아래는 가상 상속에 대해 머리를 감싸는 데 도움이 된 것입니다.
더블 A
먼저 가상 상속없이 다음 코드로 시작해 보겠습니다.
#include<iostream>
using namespace std;
class A {
public:
A() { cout << "A::A() "; }
A(int x) : m_x(x) { cout << "A::A(" << x << ") "; }
int getX() const { return m_x; }
private:
int m_x = 42;
};
class B : public A {
public:
B(int x):A(x) { cout << "B::B(" << x << ") "; }
};
class C : public A {
public:
C(int x):A(x) { cout << "C::C(" << x << ") "; }
};
class D : public C, public B {
public:
D(int x, int y): C(x), B(y) {
cout << "D::D(" << x << ", " << y << ") "; }
};
int main() {
cout << "Create b(2): " << endl;
B b(2); cout << endl << endl;
cout << "Create c(3): " << endl;
C c(3); cout << endl << endl;
cout << "Create d(2,3): " << endl;
D d(2, 3); cout << endl << endl;
// error: request for member 'getX' is ambiguous
//cout << "d.getX() = " << d.getX() << endl;
// error: 'A' is an ambiguous base of 'D'
//cout << "d.A::getX() = " << d.A::getX() << endl;
cout << "d.B::getX() = " << d.B::getX() << endl;
cout << "d.C::getX() = " << d.C::getX() << endl;
}
출력을 살펴 보겠습니다. 예상대로 실행 B b(2);
하면 다음 A(2)
과 C c(3);
같습니다.
Create b(2):
A::A(2) B::B(2)
Create c(3):
A::A(3) C::C(3)
D d(2, 3);
모두 필요 B
하고 C
그들 각각의 자신을 만드는, A
우리는 이중 그래서, A
에 d
:
Create d(2,3):
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3)
이것이 d.getX()
컴파일러가 A
메서드를 호출해야하는 인스턴스를 선택할 수 없기 때문에 컴파일 오류가 발생 하는 이유 입니다. 여전히 선택한 부모 클래스에 대해 직접 메서드를 호출 할 수 있습니다.
d.B::getX() = 3
d.C::getX() = 2
가상
이제 가상 상속을 추가하겠습니다. 다음과 같이 변경된 동일한 코드 샘플 사용 :
class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl; //uncommented
cout << "d.A::getX() = " << d.A::getX() << endl; //uncommented
...
다음 생성으로 이동합니다 d
.
Create d(2,3):
A::A() C::C(2) B::B(3) D::D(2, 3)
및의 A
생성자에서 전달 된 매개 변수를 무시하고 기본 생성자로 생성 된 것을 볼 수 있습니다 . 모호성이 사라지면 모든 호출 이 동일한 값 을 반환합니다.B
C
getX()
d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42
그러나 매개 변수화 된 생성자를 호출하려면 어떻게해야 A
합니까? 의 생성자에서 명시 적으로 호출하여 수행 할 수 있습니다 D
.
D(int x, int y, int z): A(x), C(y), B(z)
일반적으로 클래스는 직접 부모의 생성자 만 명시 적으로 사용할 수 있지만 가상 상속의 경우 제외됩니다. 이 규칙을 발견 한 것은 저에게 "클릭"되었고 가상 인터페이스를 이해하는 데 많은 도움이되었습니다.
코드 class B: virtual A
는 상속 된 모든 클래스 B
가 이제 자동으로 수행되지 않기 A
때문에 자체적으로 생성 하는 책임이 있음을 의미 B
합니다.
이 진술을 염두에두면 내가 가진 모든 질문에 쉽게 답할 수 있습니다.
D
생성 하는 동안의 매개 변수에 대한 책임 도 B
없고 , 전적으로 유일한 것입니다.C
A
D
C
의 생성을 A
에 위임 D
하지만 다이아몬드 문제를 다시 가져 오는 B
자체 인스턴스를 생성합니다.A
- 직계 하위가 아닌 손자 클래스에서 기본 클래스 매개 변수를 정의하는 것은 좋은 습관이 아니므로 다이아몬드 문제가 존재하고이 조치가 불가피 할 때 허용되어야합니다.