구조체가 상속을 지원하지 않는 이유는 무엇입니까?


128

.NET의 구조체는 상속을 지원하지 않지만 이러한 방식으로 제한되는 이유 는 명확하지 않습니다 .

구조체가 다른 구조체에서 상속되는 것을 막는 기술적 이유는 무엇입니까?


6
나는이 기능에 만족하지 않지만 구조체 상속이 유용한 몇 가지 경우를 생각할 수 있습니다. Point2D 구조체를 상속이있는 Point3D 구조체로 확장하고 싶을 수도 있고, Int32에서 상속하여 값을 제한 할 수도 있습니다. 1에서 100 사이의 경우 여러 파일에서 볼 수있는 type-def를 만들 수 있습니다 (TypeA 사용 = typeB 트릭에는 파일 범위 만 있음).
Juliet

4
stackoverflow.com/questions/1082311/… 을 읽으면 구조체에 대해 좀 더 자세히 설명하고 특정 크기로 제한해야하는 이유를 알 수 있습니다. 구조체에서 상속을 사용하려면 클래스를 사용해야합니다.
Justin

1
그리고 당신은 stackoverflow.com/questions/1222935/… 를 읽고 싶을 것입니다. 왜 dotNet 플랫폼에서 할 수 없었는지에 대해 자세히 설명합니다. 그들은 C ++ 방식으로 만들었고 관리 플랫폼에 재앙이 될 수있는 동일한 문제를 가지고 있습니다.
Dykam

@Justin 클래스에는 구조체가 피할 수있는 성능 비용이 있습니다. 그리고 정말 중요한 게임 개발에서. 따라서 어떤 경우에는 도움이된다면 수업을 사용하지 말아야합니다.
Gavin Williams

@Dykam C #에서 할 수 있다고 생각합니다. 비참함은 과장입니다. 기술에 익숙하지 않은 경우 C #으로 오늘 비참한 코드를 작성할 수 있습니다. 그래서 그것은 실제로 문제가 아닙니다. 구조체 상속이 일부 문제를 해결하고 특정 시나리오에서 더 나은 성능을 제공 할 수 있다면, 저는 그게 전부입니다.
Gavin Williams

답변:


121

값 유형이 상속을 지원할 수없는 이유는 배열 때문입니다.

문제는 성능 및 GC 이유로 인해 값 유형의 배열이 "인라인"으로 저장된다는 것입니다. 예를 들어, 소정의 new FooType[10] {...}경우, FooType참조 형식 11 개체는 관리 힙 (각 유형의 인스턴스의 배열 한, 10)에 생성한다. 경우 FooType값 대신 타입 인스턴스 만이 관리 힙에 생성 될 것이다 - 배열 자체 (각 배열의 값으로서 배열 "인라인"를 저장한다).

이제 값 유형이있는 상속이 있다고 가정합니다. 위의 배열의 "인라인 스토리지"동작과 결합하면 C ++에서 볼 수 있듯이 나쁜 일이 발생 합니다.

이 의사 C # 코드를 고려하십시오.

struct Base
{
    public int A;
}

struct Derived : Base
{
    public int B;
}

void Square(Base[] values)
{
  for (int i = 0; i < values.Length; ++i)
      values [i].A *= 2;
}

Derived[] v = new Derived[2];
Square (v);

정상적인 변환 규칙에 따라 a Derived[]Base[](더 좋든 나쁘 든) a 로 변환 할 수 있으므로 위의 예에서 s / struct / class / g하면 문제없이 예상대로 컴파일되고 실행됩니다. 그러나 경우 BaseDerived값 종류가 있으며, 배열은 우리가 문제가, 인라인 값을 저장합니다.

Square()대해 아무것도 모르기 때문에 문제 Derived가 있습니다. 배열의 각 요소에 액세스하기 위해 포인터 산술 만 사용하여 일정한 양 ( sizeof(A)) 만큼 증가합니다 . 어셈블리는 모호하게 다음과 같습니다.

for (int i = 0; i < values.Length; ++i)
{
    A* value = (A*) (((char*) values) + i * sizeof(A));
    value->A *= 2;
}

(예, 그것은 끔찍한 어셈블리이지만, 요점은 파생 된 형식이 사용되고 있다는 사실을 알지 못해도 알려진 컴파일 타임 상수에서 배열을 통해 증가한다는 것입니다.)

따라서 이것이 실제로 발생하면 메모리 손상 문제가 발생합니다. 특히, 내 Square(), values[1].A*=2것입니다 실제로 수정 될 values[0].B!

디버깅하려고 THAT !


11
이 문제에 대한 현명한 해결책은 캐스트 양식 Base []를 Detived []로 캐스트하는 것을 허용하지 않는 것입니다. short []에서 int [] 로의 캐스팅이 금지 된 것처럼 short에서 int로 캐스팅이 가능합니다.
Niki

3
+ 답변 : 상속 문제는 당신이 배열의 관점에서 볼 때까지 저와 함께 클릭하지 않았습니다. 다른 사용자는 구조체를 적절한 크기로 "슬라이싱"하여이 문제를 완화 할 수 있다고 말했습니다.하지만 슬라이싱이 해결되는 것보다 더 많은 문제의 원인이라고 생각합니다.
Juliet

4
예,하지만 배열 변환은 명시 적 변환이 아닌 암시 적 변환을위한 것이기 때문에 "이치에 맞습니다". short to int는 가능하지만 캐스트가 필요하므로 short []를 int []로 변환 할 수 없다는 것이 합리적입니다 ( 'a.Select (x => (int) x) .ToArray (와 같은 변환 코드의 부족). ) '). 런타임이 Base에서 Derived 로의 캐스트를 허용하지 않으면 IS가 참조 유형에 허용되므로 "wart"가됩니다. 따라서 우리는 두 가지 다른 "사마귀"가 가능합니다. 구조체 상속을 금지하거나 파생 된 배열을 base-of-base로 변환하는 것을 금지합니다.
jonp 2009-08-03

3
적어도 구조체 상속을 방지함으로써 우리는 별도의 키워드를 가지고 있으며, 한 집합 (클래스)에 대해 작동하지만 다른 집합 (구조)에 대해 작동하지 않는 "무작위"제한을 갖는 것과 반대로 "구조는 특별하다"라고 더 쉽게 말할 수 있습니다. . 구조체 제한이 설명하기 훨씬 쉽다고 생각합니다 ( "다르다!").
jonp

2
함수 이름을 'square'에서 'double'로 변경해야 함
John

69

구조체가 상속을 지원한다고 상상해보십시오. 다음 선언 :

BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.

a = b; //?? expand size during assignment?

구조체 변수의 크기가 고정되어 있지 않다는 것을 의미하므로 참조 유형이 있습니다.

더 좋은 점은 다음과 같습니다.

BaseStruct[] baseArray = new BaseStruct[1000];

baseArray[500] = new InheritedStruct(); //?? morph/resize the array?

5
C ++는 '슬라이싱'이라는 개념을 도입하여 이에 대한 답을 얻었으므로 해결할 수있는 문제입니다. 그렇다면 struct 상속이 지원되지 않는 이유는 무엇입니까?
jonp 2009-08-03

1
상속 가능한 구조체의 배열을 고려하고 C #은 (메모리) 관리 언어라는 점을 기억하십시오. 슬라이싱 또는 이와 유사한 옵션은 CLR의 기본 사항에 혼란을 초래할 수 있습니다.
Kenan EK

4
@jonp : 해결 가능합니다. 바람직한? 다음은 사고 실험입니다. 기본 클래스 Vector2D (x, y)와 파생 클래스 Vector3D (x, y, z)가 있다고 상상해보십시오. 두 클래스에는 각각 sqrt (x ^ 2 + y ^ 2) 및 sqrt (x ^ 2 + y ^ 2 + z ^ 2)를 계산하는 Magnitude 속성이 있습니다. 'Vector3D a = Vector3D (5, 10, 15); Vector2D b = a; ','a.Magnitude == b.Magnitude '는 무엇을 반환해야합니까? 그런 다음 'a = (Vector3D) b'라고 쓰면 a.Magnitude가 할당 이전과 이후와 동일한 값을 갖습니까? .NET 설계자들은 아마도 "아니, 우리는 그 어떤 것도 없을 것"이라고 스스로에게 말했을 것입니다.
Juliet

1
문제가 해결 될 수 있다고해서 해결되어야한다는 의미는 아닙니다. 때로는 문제가 발생하는 상황을 피하는 것이 가장 좋습니다.
Dan Diplo

@ kek444 : 구조체 Foo상속 Bar을 사용하면 a Foo가에 할당되는 것을 허용해서는 안되지만 Bar이러한 방식으로 구조체를 선언하면 몇 가지 유용한 효과를 얻을 수 있습니다. (1) Bar에서 첫 번째 항목으로 특수 이름의 멤버를 Foo만들고 Foo포함 멤버 이름에 그 회원들에게 별명 것을 Bar사용했다 코드를 허용 Bar적응 할 수는를 사용하는 Foo모든 참조를 교체하지 않고, 대신 thing.BarMemberthing.theBar.BarMember, 읽고 모두 쓰는 기능 유지 Bar그룹으로 필드의 '; ...
supercat

14

구조체는 참조를 사용하지 않습니다 (박스형이 아니면 피해야합니다). 따라서 참조 포인터를 통한 간접 지정이 없기 때문에 다형성은 의미가 없습니다. 객체는 일반적으로 힙에 있으며 참조 포인터를 통해 참조되지만 구조체는 스택에 할당되거나 (박스형이 아닌 경우) 힙의 참조 유형이 차지하는 메모리 "내부"에 할당됩니다.


8
상속을 이용하기 위해 다형성을 사용할 필요가 없습니다
rmeador

그렇다면 .NET에는 몇 가지 유형의 상속이 있습니까?
John Saunders

다형성은 구조체에 존재합니다. 사용자 지정 구조체에서 구현할 때 또는 ToString ()의 사용자 지정 구현이없는 경우 ToString () 호출의 차이점을 고려하십시오.
Kenan EK

모두 System.Object에서 파생되기 때문입니다. 구조체보다는 System.Object 유형의 다형성에 가깝습니다.
John Saunders

다형성은 제네릭 유형 매개 변수로 사용되는 구조에서 의미가있을 수 있습니다. 다형성은 인터페이스를 구현하는 구조체와 함께 작동합니다. 인터페이스의 가장 큰 문제는 struct 필드에 byref를 노출 할 수 없다는 것입니다. 그렇지 않으면 구조체를 "상속"하는 한 가장 큰 도움이 될 것이라고 생각하는 것은 구조체 형식 Foo의 필드 를 가진 형식 (구조체 또는 클래스) 을 갖는 수단이 의 멤버를 자신의 Bar것으로 간주 할 수 Bar있다는 것입니다. Point3d클래스 수 예를 들어, 캡슐화 Point2d xy하지만이 참조 X중 하나로 해당 필드의 xy.XX.
supercat

8

문서 의 내용 다음과 같습니다 .

구조체는 값 의미가있는 작은 데이터 구조에 특히 유용합니다. 복소수, 좌표계의 점 또는 사전의 키-값 쌍은 모두 구조체의 좋은 예입니다. 이러한 데이터 구조의 핵심은 데이터 멤버가 적고 상속 또는 참조 ID를 사용할 필요가 없으며 할당이 참조 대신 값을 복사하는 값 의미 체계를 사용하여 편리하게 구현할 수 있다는 것입니다.

기본적으로 간단한 데이터를 보유해야하므로 상속과 같은 "추가 기능"이 없습니다. 기술적으로는 제한된 종류의 상속 (다형성이 아니라 스택에 있기 때문에)을 지원하는 것이 기술적으로 가능할 수 있지만 상속을 지원하지 않는 것이 설계 선택이라고 생각합니다 (.NET의 다른 많은 것들처럼) 언어가 있습니다.)

다른 한편으로 나는 상속의 이점에 동의하고 우리 모두가 struct다른 사람으로부터 물려 받기를 원하는 지점에 도달했고 그것이 불가능하다는 것을 깨달았다 고 생각합니다. 그러나 그 시점에서 데이터 구조는 아마도 너무 발전하여 어쨌든 클래스가되어야합니다.


4
그것이 상속이없는 이유가 아닙니다.
Dykam

나는 두 개의 구조체를 사용할 수없는 한 상속 존재가 여기에 대해 이야기를 믿는 경우 하나의 상호 교환 다른로부터 상속하지만, 다시 사용하고 (즉, 만드는 또 다른 하나의 구조체의 구현에 추가 Point3DA로부터를 Point2D, 당신은 할 수 없을 것입니다 사용하는 Point3D대신의를 Point2D, 그러나 당신은 다시 구현하지 않았을 Point3D...의 내가 어쨌든 그것을 해석하는 방법입니다) 완전히 처음부터.
Blixt

요약 하면 다형성없이 상속을 지원할 있습니다. 그렇지 않습니다. 나는 사람이 선택할 수있는 디자인 선택의 생각 class을 통해 struct적절한 경우.
Blixt

3

구조체가 스택에 직접 배치되기 때문에 상속과 같은 클래스는 불가능합니다. 상속하는 구조체는 부모보다 더 클 것이지만 JIT는이를 모르고 너무 적은 공간에 너무 많은 것을 넣으려고합니다. 약간 불분명하게 들리면 예제를 작성해 보겠습니다.

struct A {
    int property;
} // sizeof A == sizeof int

struct B : A {
    int childproperty;
} // sizeof B == sizeof int * 2

이것이 가능하면 다음 스 니펫에서 충돌이 발생합니다.

void DoSomething(A arg){};

...

B b;
DoSomething(b);

공간은 B의 크기가 아니라 A의 크기에 할당됩니다.


2
C ++는이 경우를 잘 처리합니다. IIRC. B의 인스턴스는 A의 크기에 맞게 슬라이스됩니다. .NET 구조체와 같이 순수한 데이터 유형이면 나쁜 일이 발생하지 않습니다. A를 반환하는 메서드에 약간의 문제가 발생하고 해당 반환 값을 B에 저장하지만 허용해서는 안됩니다. 요컨대, .NET 디자이너 원하는 경우이를 처리 할 수 있었지만 어떤 이유로 든 처리하지 않았습니다.
rmeador

1
DoSomething ()의 경우 (C ++ 의미론 가정) 'b'가 A 인스턴스를 생성하기 위해 "슬라이스"되므로 문제가 없을 가능성이 높습니다. 문제는 <i> 배열 </ i>에 있습니다. 기존 A 및 B 구조체와 <c> DoSomething (A [] arg) {arg [1] .property = 1;} </ c> 메서드를 고려하십시오. 값 유형의 배열은 "인라인"값을 저장하므로 DoSomething (actual = new B [2] {})은 actual [1] .property가 아니라 actual [0] .childproperty가 설정되도록합니다. 이것은 나쁘다.
jonp 2009-08-03

2
@John : 나는 그것이 있다고 주장하지 않았고, @jonp도 마찬가지라고 생각합니다. 우리는 단지이 문제가 오래되었고 해결되었다고 언급했을 뿐이므로 .NET 설계자들은 기술적 불가능 성 이외의 이유로이를 지원하지 않기로 결정했습니다.
rmeador

"파생 된 유형의 배열"문제는 C ++의 새로운 문제가 아닙니다. parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.4 참조 (C ++의 배열은 악합니다! ;-)
jonp 2009-08-03

@John : "파생 된 유형의 배열과 기본 유형이 혼합되지 않음"문제에 대한 해결책은 평소와 같이하지 마십시오. 이것이 C ++의 배열이 악의적 인 이유 (메모리 손상을 더 쉽게 허용 함)이고 .NET이 값 유형 (컴파일러와 JIT가 발생하지 않도록 보장)을 통한 상속을 지원하지 않는 이유입니다.
jonp 2009-08-03

3

수정하고 싶은 점이 있습니다. 구조체가 상속 될 수없는 이유는 스택에있는 구조가 올바른 것이기 때문이지만, 동일한 설명은 절반이 정확합니다. 다른 값 유형과 마찬가지로 구조체 는 스택에 있을 수 있습니다 . 변수가 선언 된 위치에 따라 다르기 때문에 스택 또는 힙에 위치 합니다. 이것은 그들이 각각 지역 변수 또는 인스턴스 필드 일 때입니다.

그렇게 말하면서 Cecil은 이름이 있습니다.

저는 이것을 강조하고 싶습니다. 값 유형 스택에 존재할 수 있습니다 . 그렇다고 항상 그렇게한다는 의미는 아닙니다. 메소드 매개 변수를 포함한 지역 변수가 사용됩니다. 다른 모든 것은 그렇지 않습니다. 그럼에도 불구하고 상속 될 수없는 이유는 여전히 남아 있습니다. :-)


3

구조체는 스택에 할당됩니다. 이것은 값 의미 체계가 거의 무료이며 구조체 멤버에 액세스하는 것이 매우 저렴하다는 것을 의미합니다. 이것은 다형성을 방지하지 않습니다.

가상 함수 테이블에 대한 포인터로 각 구조체를 시작할 수 있습니다. 이것은 성능 문제가 될 수 있지만 (모든 구조체는 적어도 포인터의 크기가 됨) 가능합니다. 이것은 가상 기능을 허용합니다.

필드 추가는 어떻습니까?

글쎄, 스택에 구조체를 할당 할 때 특정 공간을 할당합니다. 필요한 공간은 컴파일 시간에 결정됩니다 (사전 또는 JIT 팅시). 필드를 추가 한 다음 기본 유형에 할당하는 경우 :

struct A
{
    public int Integer1;
}

struct B : A
{
    public int Integer2;
}

A a = new B();

스택의 알려지지 않은 부분을 덮어 씁니다.

대안은 런타임이 A 변수에 sizeof (A) 바이트 만 기록하여이를 방지하는 것입니다.

B가 A의 메서드를 재정의하고 Integer2 필드를 참조하면 어떻게됩니까? 런타임에서 MemberAccessException이 발생하거나 메서드가 대신 스택의 일부 임의 데이터에 액세스합니다. 둘 다 허용되지 않습니다.

구조체를 다형성으로 사용하지 않거나 상속 할 때 필드를 추가하지 않는 한 구조체 상속을 사용하는 것이 완벽하게 안전합니다. 그러나 이것들은별로 유용하지 않습니다.


1
거의. 다른 누구도 스택과 관련하여 슬라이싱 문제를 언급하지 않았으며 어레이와 관련해서 만 언급했습니다. 그리고 아무도 사용 가능한 솔루션에 대해 언급하지 않았습니다.
user38001 2009-08-03

1
.net의 모든 값 유형은 유형 또는 포함 된 필드에 관계없이 creastion에서 0으로 채워집니다. vtable 포인터와 같은 것을 구조체에 추가하려면 0이 아닌 기본값으로 유형을 초기화하는 수단이 필요합니다. 이러한 기능은 다양한 목적에 유용 할 수 있으며 대부분의 경우 이러한 기능을 구현하는 것은 그리 어렵지 않을 수 있지만 .net에는 가까운 것이 없습니다.
supercat

@ user38001 "구조는 스택에 할당됩니다"-힙에 할당되는 인스턴스 필드가 아닌 경우.
데이비드 클렘 프너

2

이것은 매우 빈번한 질문처럼 보입니다. 값 유형이 변수를 선언하는 "제자리에"저장된다는 점을 추가하고 싶습니다. 구현 세부 사항과는 별개로, 이것은 객체에 대해 어떤 것을 말하는 객체 헤더 가 없다는 것을 의미 하며 변수 만이 거기에 어떤 종류의 데이터가 있는지 알고 있습니다.


컴파일러는 거기에 무엇이 있는지 알고 있습니다. C ++를 참조하면 이것이 답이 될 수 없습니다.
Henk Holterman

어디에서 C ++를 추론 했습니까? 이것이 바로 동작과 가장 잘 일치하는 것이기 때문에, 스택은 MSDN 블로그 기사를 인용하기 위해 구현 세부 사항이므로 제자리에서 말하겠습니다.
세실은 이름 가지고

예, C ++를 언급하는 것은 나빴습니다. 그러나 런타임 정보가 필요한지에 대한 질문을 제외하고 왜 구조체에 '객체 헤더'가 없어야합니까? 컴파일러는 원하는대로 매쉬업 할 수 있습니다. [Structlayout] 구조체의 헤더를 숨길 수도 있습니다.
Henk Holterman

구조체는 값 형식이기 때문에 런타임은 항상 다른 값 형식 (제약 조건)과 마찬가지로 콘텐츠를 복사하므로 개체 헤더가 필요하지 않습니다. 헤더는 의미가 없습니다. 참조 유형 클래스는 다음과 같습니다. P
Cecil Has a Name

1

구조체는 인터페이스를 지원하므로 이러한 방식으로 다형성을 수행 할 수 있습니다.


0

IL은 스택 기반 언어이므로 인수를 사용하여 메서드를 호출하면 다음과 같이됩니다.

  1. 인수를 스택으로 푸시
  2. 메서드를 호출하십시오.

메서드가 실행되면 스택에서 일부 바이트를 꺼내 인수를 얻습니다. 정확히 안다인수가 참조 유형 포인터 (32 비트에서는 항상 4 바이트)이거나 크기가 항상 정확하게 알려진 값 유형이기 때문에 얼마나 많은 바이트를 팝 오프할지 있습니다.

참조 유형 포인터 인 경우 메소드는 힙에서 객체를 찾고 정확한 유형에 대한 특정 메소드를 처리하는 메소드 테이블을 가리키는 유형 핸들을 가져옵니다. 값 유형 인 경우 값 유형이 상속을 지원하지 않기 때문에 메소드 테이블에 대한 조회가 필요하지 않으므로 가능한 메소드 / 유형 조합이 하나뿐입니다.

값 유형이 상속을 지원하는 경우 특정 유형의 구조체가 해당 값뿐만 아니라 스택에 배치되어야한다는 추가 오버 헤드가 발생합니다. 이는 유형의 특정 구체적인 인스턴스에 대한 일종의 메서드 테이블 조회를 의미합니다. 이것은 값 유형의 속도 및 효율성 이점을 제거합니다.


1
C ++로 해결되었습니다. 실제 문제에 대한 답변을 읽어보십시오. stackoverflow.com/questions/1222935/…
Dykam
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.