문제

C# 또는 Java의 다음 버전에 여러 상속이 포함되어야하는지 항상 묻는 사람들을 볼 수 있습니다. 이 능력을 가질만큼 운이 좋은 C ++ 사람들은 이것이 누군가에게 결국 스스로를 걸 수있는 밧줄을주는 것과 같다고 말합니다.

여러 상속이 무엇입니까? 콘크리트 샘플이 있습니까?

도움이 되었습니까?

해결책

가장 분명한 문제는 기능을 우선하는 것입니다.

두 가지 수업이 있다고 가정 해 봅시다 A 그리고 B, 둘 다 메소드를 정의합니다 doSomething. 이제 당신은 제 3 수업을 정의합니다 C, 두 가지에서 물려받습니다 A 그리고 B, 그러나 당신은 그것을 무시하지 않습니다 doSomething 방법.

컴파일러 가이 코드를 시드 할 때 ...

C c = new C();
c.doSomething();

... 어떤 방법의 구현을 사용해야합니까? 더 이상 설명하지 않으면 컴파일러가 모호성을 해결하는 것은 불가능합니다.

재정의 외에도 여러 상속의 다른 큰 문제는 메모리의 물리적 물체의 레이아웃입니다.

C ++ 및 Java 및 C#과 같은 언어는 각 유형의 객체에 대해 고정 주소 기반 레이아웃을 만듭니다. 이 같은:

class A:
    at offset 0 ... "abc" ... 4 byte int field
    at offset 4 ... "xyz" ... 8 byte double field
    at offset 12 ... "speak" ... 4 byte function pointer

class B:
    at offset 0 ... "foo" ... 2 byte short field
    at offset 2 ... 2 bytes of alignment padding
    at offset 4 ... "bar" ... 4 byte array pointer
    at offset 8 ... "baz" ... 4 byte function pointer

컴파일러가 기계 코드 (또는 바이트 코드)를 생성하면 해당 숫자 오프셋을 사용하여 각 방법 또는 필드에 액세스합니다.

다중 상속은 매우 까다 롭습니다.

클래스 인 경우 C 둘 다 물려납니다 A 그리고 B, 컴파일러는 데이터를 레이아웃할지 여부를 결정해야합니다. AB 주문 또는에 BA 주문하다.

그러나 이제 당신이 a에서 방법을 호출한다고 상상해보십시오 B 물체. 정말 그냥 a B? 아니면 실제로는입니다 C 물체는 다형적으로, 그를 통해 다형성이라고 불립니다 B 상호 작용? 객체의 실제 아이덴티티에 따라 물리적 레이아웃은 다르며 콜 사이트에서 호출 할 함수의 오프셋을 알 수 없습니다.

이러한 종류의 시스템을 처리하는 방법은 고정 층 접근 방식을 버리고 레이아웃에 대해 각 객체를 쿼리 할 수있는 것입니다. ~ 전에 함수를 호출하거나 필드에 액세스하려고 시도합니다.

그래서 ... 긴 이야기가 짧습니다 ... 컴파일러 저자가 여러 상속을 지원하는 것은 목에 고통입니다. 따라서 Guido van Rossum과 같은 누군가가 Python을 디자인하거나 Anders Hejlsberg가 C#을 디자인 할 때, 여러 상속을 지원하는 것이 컴파일러 구현을 훨씬 더 복잡하게 만들 것이라는 것을 알고 있으며 아마도 그 이점이 비용의 가치가 있다고 생각하지 않을 것입니다.

다른 팁

당신이 언급 한 문제는 실제로 해결하기 어렵지 않습니다. 실제로 예를 들어 에펠 펠은 완벽하게 잘합니다! (그리고 임의의 선택이나 무엇이든 소개하지 않고)

예를 들어 A와 B에서 상속 된 경우 FOO () 메소드가있는 경우 물론 A와 B 모두에서 Class C에서 임의의 선택을 원하지 않습니다. C.foo ()가 호출되거나 그렇지 않으면 사용되는 경우 사용됩니다.

또한 여러 상속이 종종 매우 유용하다고 생각합니다. 에펠의 라이브러리를 보면 모든 곳에서 사용되며 개인적으로 Java의 프로그래밍으로 돌아 가야 할 때 기능을 놓쳤다는 것을 알 수 있습니다.

다이아몬드 문제:

두 클래스 B와 C가 A에서 상속 될 때 발생하는 모호성 및 클래스 D는 B와 C 모두에서 상속됩니다. 우선, 그리고 d는 그것을 무시하지 않으면 어떤 버전의 방법이 d 상속되는지 : b의 또는 c의 버전은 무엇입니까?

...이 상황에서 클래스 상속 다이어그램의 모양 때문에 "다이아몬드 문제"라고합니다. 이 경우 클래스 A는 맨 위에 B와 C가 모두 별도로 있으며, D는 바닥에서 두 개를 함께 결합하여 다이아몬드 모양을 형성합니다 ...

다중 상속은 자주 사용되지 않고 오용 할 수 있지만 때로는 필요한 것들 중 하나입니다.

좋은 대안이 없을 때 오용 될 수 있기 때문에 기능을 추가하지 않는 것을 이해하지 못했습니다. 인터페이스는 다중 상속에 대한 대안이 아닙니다. 우선, 그들은 당신이 전제 조건이나 사후 조건을 시행 할 수 없습니다. 다른 도구와 마찬가지로 사용하기에 적절한시기와 사용 방법을 알아야합니다.

C에 의해 상속되는 객체 A와 B가 있다고 가정 해 봅시다. a와 b는 모두 foo ()를 구현하고 c는 그렇지 않습니다. 나는 c.foo ()를 호출한다. 어떤 구현이 선택됩니까? 다른 문제가 있지만 이러한 유형의 문제는 큰 문제입니다.

다중 상속의 주요 문제는 Tloach의 예와 함께 요약됩니다. 동일한 함수 또는 필드를 구현하는 여러 기본 클래스에서 상속 할 때 컴파일러는 상속 할 구현에 대한 결정을 내려야합니다.

동일한 기본 클래스에서 상속되는 여러 클래스에서 상속 될 때 더 나빠집니다. (다이아몬드 상속, 상속 트리를 그리면 다이아몬드 모양이됩니다)

이러한 문제는 컴파일러가 극복하는 데 실제로 문제가되지 않습니다. 그러나 컴파일러가 여기서 선택해야 할 선택은 다소 임의적이며 코드는 훨씬 덜 직관적입니다.

좋은 oo 디자인을 할 때 여러 상속이 필요하지 않다는 것을 알았습니다. 필요한 경우 일반적으로 상속을 사용하여 기능을 재사용하는 반면 상속은 "IS-A"관계에만 적합합니다.

Mixins와 같은 다른 기술에는 동일한 문제를 해결하고 여러 상속이 가지고있는 문제가 없습니다.

나는 다이아몬드 문제가 문제라고 생각하지 않습니다. 나는 그 소위 소, 다른 것을 고려할 것입니다.

내 관점에서 볼 때 최악의 문제는 다수의 상속 재산입니다. RAD - 피해자와 개발자라고 주장하지만 실제로는 반으로 지식 (최상의)이 붙어 있습니다.

개인적으로, 나는 이와 같은 Windows 양식에서 마침내 무언가를 할 수 있다면 매우 행복 할 것입니다 (올바른 코드는 아니지만 아이디어를 제공해야합니다).

public sealed class CustomerEditView : Form, MVCView<Customer>

이것은 여러 상속이없는 주요 문제입니다. 당신은 인터페이스와 비슷한 일을 할 수 있지만, 내가 "s *** code"라고 부르는 것이 있습니다. 예를 들어 데이터 컨텍스트를 얻으려면 각 클래스에 작성해야합니다.

제 생각에는 현대 언어로 코드를 반복 할 때는 절대적으로 필요하지 않아야합니다.

공통 LISP 객체 시스템 (CLOS)은 C ++-스타일 문제를 피하면서 MI를 지원하는 또 다른 예입니다. 상속은 현명한 기본값, 여전히 당신에게 슈퍼의 행동을 정확하게 부를 수있는 방법을 명시 적으로 결정할 수있는 자유를 허용하면서.

여러 상속 자체에는 잘못된 것이 없습니다. 문제는 처음부터 여러 상속을 염두에두고 설계되지 않은 언어에 여러 상속을 추가하는 것입니다.

에펠 펠 언어는 매우 효율적이고 생산적인 방식으로 제한없이 여러 상속을 지원하고 있지만 언어는 그 시작부터 지원하기 위해 설계되었습니다.

이 기능은 컴파일러 개발자를 위해 구현하기가 복잡하지만, 우수한 다중 상속 지원이 다른 기능의 지원을 피할 수 있다는 사실 (즉, 인터페이스 또는 확장 방법이 필요하지 않음).

여러 상속을지지하는 것은 선택의 문제, 우선 순위의 문제라고 생각합니다. 보다 복잡한 기능은 올바르게 구현되고 운영되는 데 더 많은 시간이 걸리며 더 논란이 될 수 있습니다. C ++ 구현은 C# 및 Java에서 다중 상속이 구현되지 않은 이유 일 수 있습니다 ...

Java 및 .NET와 같은 프레임 워크의 디자인 목표 중 하나는 사전 컴파일 된 라이브러리의 한 버전과 함께 작동하도록 컴파일 된 코드가 가능하게하여 후속 버전의 후속 버전과 동일하게 작동하도록하는 것입니다. 새로운 기능을 추가하십시오. C 또는 C ++와 같은 언어의 일반적인 패러다임은 필요한 모든 라이브러리를 포함하는 정적으로 연결된 실행 파이브를 배포하는 것이지만 .NET 및 Java의 패러다임은 런타임에 "연결된"구성 요소 모음으로 응용 프로그램을 배포하는 것입니다. .

.NET에 앞서있는 COM 모델은이 일반적인 접근법을 사용하려고 시도했지만 실제로 상속 재산이 없었으며, 각 클래스 정의에는 모든 공개 구성원이 포함 된 동일한 이름의 클래스와 인터페이스를 효과적으로 정의했습니다. 인스턴스는 클래스 유형이며 참조는 인터페이스 유형이었습니다. 클래스가 다른 사람에게서 파생 된 것으로 선언하는 것은 클래스가 상대방의 인터페이스를 구현하는 것으로 선언하는 것과 동일하며, 새로운 클래스는 한 클래스의 모든 공개 멤버를 다시 구현해야했습니다. y와 z가 x에서 파생 된 다음 y와 z에서 파생된다면 z와 z가 X의 멤버를 다르게 구현하는지 여부는 중요하지 않습니다. z는 구현을 사용할 수 없기 때문에 그 일을 정의해야합니다. 소유하다. w는 y 및/또는 z의 인스턴스를 캡슐화하고 X의 메소드 구현을 그들의 구현을 체인 할 수 있지만 X의 방법에 대한 모호성은 없을 것입니다. Z의 코드가 명시 적으로 지시 한 모든 일을 수행해야합니다.

Java와 .NET의 어려움은 코드가 멤버를 상속하고 액세스 할 수 있다는 것입니다. 암시 적으로 부모 구성원을 참조하십시오. 위와 같이 WZ와 관련된 클래스가 있다고 가정합니다.

class X { public virtual void Foo() { Console.WriteLine("XFoo"); }
class Y : X {};
class Z : X {};
class W : Y, Z  // Not actually permitted in C#
{
  public static void Test()
  {
    var it = new W();
    it.Foo();
  }
}

좋아 보일 것 같습니다 W.Test() W 인스턴스를 작성해야합니다. 가상 방법의 구현을 호출해야합니다. Foo 정의되었습니다 X. 그러나 y와 z가 실제로 별도로 컴파일 된 모듈에 있었고, x와 w가 컴파일 될 때 위에서 정의되었지만 나중에 변경되고 재 컴파일되었다고 가정합니다.

class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }

이제 전화의 효과는 무엇입니까? W.Test()? 프로그램이 배포 전에 정적으로 연결되어야한다면, 정적 링크 단계는 Y와 Z가 변경되기 전에 프로그램이 모호성이 없지만 Y와 Z의 변경으로 인해 모호한 일이 있었으며 링커가 거부 할 수 있음을 분별할 수 있습니다. 그러한 모호성이 해결 될 때까지 프로그램을 구축하십시오. 반면에 W와 새로운 버전의 Y와 Z를 가진 사람은 단순히 프로그램을 실행하고 싶고 그 중 하나에 대한 소스 코드가없는 사람 일 수 있습니다. 언제 W.Test() 달리기, 더 이상 무엇을 명확하지 않을 것입니다 W.Test() 그렇게해야하지만 사용자가 새 버전의 y와 z로 W를 실행하려고 할 때까지 시스템의 어떤 부분이 문제가 있음을 인식 할 수있는 방법은 없습니다 (W가 y와 z의 변경이 전에도 불법으로 간주되지 않는 한). .

다이아몬드는 문제가되지 않습니다. ~하지 않다 C ++ 가상 상속과 같은 사용 : 정상적인 상속에서 각 기본 클래스는 멤버 필드 (실제로 RAM으로 배치되어 있음)와 비슷하여 구문 설탕과 더 많은 가상 방법을 무시할 수있는 추가 기능을 제공합니다. 그것은 컴파일 타임에 약간의 모호성을 부과 할 수 있지만 일반적으로 해결하기가 쉽습니다.

반면에 가상 상속이 너무 쉽게 통제되지 않게됩니다 (그리고 혼란이됩니다). 예를 들어“심장”다이어그램을 고려하십시오.

  A       A
 / \     / \
B   C   D   E
 \ /     \ /
  F       G
    \   /
      H

C ++에서는 완전히 불가능합니다. F 그리고 G 단일 클래스로 병합됩니다 AS도 병합되어 있습니다. 즉, C ++에서 기본 클래스를 불투명하게 고려하지 않을 수도 있습니다 (이 예에서는 구성해야합니다. A 안에 H 따라서 계층 구조 어딘가에 존재한다는 것을 알아야합니다). 그러나 다른 언어에서는 효과가있을 수 있습니다. 예를 들어, F 그리고 G "내부"로 명시 적으로 선언 할 수 있으므로 결과적으로 병합을 금지하고 효과적으로 자신을 견고하게 만들 수 있습니다.

또 다른 흥미로운 예 (~ 아니다 C ++-특정) :

  A
 / \
B   B
|   |
C   D
 \ /
  E

여기서만 B 가상 상속을 사용합니다. 그래서 E 두 개를 포함합니다 B동일하게 공유하는 s A. 이런 식으로, 당신은 얻을 수 있습니다 A* 포인터가 가리 킵니다 E, 그러나 당신은 그것을 a에 던질 수 없습니다 B* 대상이지만 포인터 ~이다 실제로 B 이러한 캐스트는 모호하며 컴파일 시간 에이 모호성을 감지 할 수 없습니다 (컴파일러가 전체 프로그램을 보지 않는 한). 테스트 코드는 다음과 같습니다.

struct A { virtual ~A() {} /* so that the class is polymorphic */ };
struct B: virtual A {};
struct C: B {};
struct D: B {};
struct E: C, D {};

int main() {
        E data;
        E *e = &data;
        A *a = dynamic_cast<A *>(e); // works, A is unambiguous
//      B *b = dynamic_cast<B *>(e); // doesn't compile
        B *b = dynamic_cast<B *>(a); // NULL: B is ambiguous
        std::cout << "E: " << e << std::endl;
        std::cout << "A: " << a << std::endl;
        std::cout << "B: " << b << std::endl;
// the next casts work
        std::cout << "A::C::B: " << dynamic_cast<B *>(dynamic_cast<C *>(e)) << std::endl;
        std::cout << "A::D::B: " << dynamic_cast<B *>(dynamic_cast<D *>(e)) << std::endl;
        std::cout << "A=>C=>B: " << dynamic_cast<B *>(dynamic_cast<C *>(a)) << std::endl;
        std::cout << "A=>D=>B: " << dynamic_cast<B *>(dynamic_cast<D *>(a)) << std::endl;
        return 0;
}

또한 구현은 매우 복잡 할 수 있습니다 (언어에 따라 다릅니다. Benjismith의 답변 참조).

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top