struct 는 값을 담고 class 는 주소를 담는다
앞에서 값 타입과 참조 타입을 갈랐습니다. int 는 값을 직접 담고, Dog 는 주소를 담았습니다.
그때 값 타입 목록은 int, double, bool, char 처럼 이미 있는 것들이었습니다.
그런데 그 목록에 우리가 만든 것을 추가할 수 있습니다.
class대신struct라고 쓰면 값 타입이 됩니다. 힙에 안 가고, 대입하면 통째로 복사됩니다.
struct 는 이렇게 생겼다
class 자리에 struct 라고 쓰는 것이 전부입니다.
struct 점
{
public int X;
public int Y;
}
쓰는 법도 똑같이 생겼습니다.
점 p = new 점();
p.X = 3;
p.Y = 4;
new 를 쓰는 모양까지 같습니다. 그런데 이 new 는 힙에 자리를 잡지 않습니다.
struct 에서 new 는 “자리를 만들어라” 가 아니라 “생성자를 불러 채워라” 에 가깝습니다.
자리는 이미 p 라는 변수 그 자체입니다.
대입하면 통째로 복사된다
이게 유일하게 중요한 차이입니다. 나머지는 전부 여기서 나옵니다.
struct 로 바꾸면 힙이 아예 등장하지 않습니다.
같은 코드에서 결과가 갈립니다.
b.X = 99;
Console.WriteLine(a.X);
점 이 class 면 99, struct 면 3 입니다. 코드는 한 글자도 안 달라졌는데 답이 다릅니다.
메서드에 넘길 때도 같다
메서드 편에서 “넘어가는 것은 값의 사본이다” 라고 하고, 왜 결과가 다른지는 미뤄 뒀습니다. 그 답이 이것입니다.
넘어가는 것이 사본이라는 규칙은 언제나 같습니다. 다만 무엇의 사본인지가 다릅니다.
| 무엇이 복사되나 | 안에서 필드를 고치면 | |
|---|---|---|
struct |
값 전체 | 바깥에 반영 안 됨 |
class |
주소 | 바깥에 반영됨 |
class 를 넘기면 주소가 복사됩니다. 주소는 복사됐지만 그 주소가 가리키는 상자는 하나입니다.
그래서 안에서 dog.Age = 5 를 하면 바깥에서도 5 입니다.
struct 를 넘기면 값 전체가 복사됩니다. 안에서 p.X = 5 를 해도 바깥의 p 는 그대로입니다.
바깥까지 바꾸고 싶으면 ref 를 붙여야 합니다. ref 가 있는 이유가 여기 있습니다.
왜 나눠 놨나
int 하나를 쓸 때마다 힙에 자리를 잡고 주소를 들고 다닌다고 생각해 보면 답이 나옵니다.
4바이트 값 하나를 위해 힙에 상자를 만들고, 주소 8바이트를 스택에 담고, 나중에 GC 가 그 상자를 치웁니다. 값 자체보다 값을 관리하는 비용이 더 큽니다.
작고 개수가 많은 것은 그냥 자리에 담는 편이 낫습니다. 그래서 값 타입이 있습니다.
Unity 를 보면 이 판단이 그대로 보입니다.
Vector3 위치 = transform.position;
Vector3 는 float 셋입니다. 게임 한 프레임에 이런 것이 수만 개씩 오갑니다.
이게 class 였으면 프레임마다 힙에 수만 개가 생기고 GC 가 계속 돌았을 겁니다.
그래서 Vector3, Quaternion, Color, Rect 는 전부 struct 입니다.
함정 하나: 값을 꺼내면 사본이다
이걸 모르면 반드시 한 번은 막힙니다.
transform.position.x = 5f; // 컴파일 에러
transform.position 은 필드가 아니라 프로퍼티입니다. 프로퍼티는 값을 돌려줍니다.
Vector3 는 struct 라서 돌려받는 것이 사본입니다.
즉 이 코드는 “사본을 하나 만들고, 그 사본의 x 를 고치고, 버려라” 가 됩니다. 아무 일도 안 일어나는 코드라서 컴파일러가 미리 막습니다.
제대로 하려면 통째로 다시 넣어야 합니다.
Vector3 p = transform.position;
p.x = 5f;
transform.position = p;
같은 이유로 List<점> 안의 원소도 list[0].X = 5 로 못 고칩니다. list[0] 이 사본이기 때문입니다.
배열은 됩니다. 배열의 인덱서는 사본이 아니라 그 자리를 직접 가리키기 때문입니다.
같아 보이는 둘이 다르게 동작하는 자리라 헷갈리기 쉽습니다.
그래서 언제 struct 로 만드나
세 가지가 다 맞을 때만 값 타입으로 만드는 편이 안전합니다.
- 작다. 대략 16바이트 안쪽. 크면 복사 비용이 주소 복사보다 비싸집니다
- 만든 뒤에 안 바꾼다. 좌표, 색, 시간처럼 통째로 갈아 끼우는 것
- 개수가 많다. 하나 만들고 마는 것이면 굳이 struct 일 이유가 없습니다
셋 중 하나라도 어긋나면 class 가 낫습니다. 특히 바뀌는 값을 struct 에 담으면
위에서 본 사본 함정이 계속 발목을 잡습니다.
정리
class대신struct라고 쓰면 값 타입이 됩니다- 대입하면 주소가 아니라 값 전체가 복사됩니다. 그래서 서로 영향을 안 줍니다
- 힙에 안 갑니다. 작고 많은 것을 담을 때 관리 비용이 사라집니다
Vector3가 struct 라서transform.position.x = 5가 컴파일 에러입니다- 작고, 안 바뀌고, 개수가 많을 때만 씁니다