Home 얕은 복사와 깊은 복사
Post
Cancel

얕은 복사와 깊은 복사

업무를 진행하다가 예상하지 못한 이슈를 발견했었고 이를 해결하면서
다시 실수하지 않기 위해 정리 했습니다.

1. 무슨 문제가 발생했었는데??

문제 상황을 예시로 설명하면,

상점에 아이템이 100개가 있고 100개 중에 10개를 뽑은 후 특수 기술이 적용되어 있는지 체크를 해주어야 하는 기능을 구현 했는데

특수 기술이 적용되지 않은 아이템에도 특수 기술이 적용되어 나오는 문제가 발생했다.

즉,

뽑은 10개의 아이템 중 A라는 아이템을 2번 뽑았고

처음 뽑은 A에는 특수 기술이 존재하고 두 번째로 뽑은 A에는 특수 기술이 존재하지 않는데

두 번째로 뽑은 A에 특수 기술이 존재하는 이슈가 발생한 것이다.

위의 문제가 위험하고 중요하다고 생각하는 게 데이터가 꼬이는 것 뿐 에러가 발생하는 것이 아니기 때문에 자세히 확인하기 않으면 놓칠 수 있기 때문이다.

2. 얕은 복사와 깊은 복사

위 문제의 해결 방법은 간단했다.

참조 형식인 아이템 class를 복사할 때 얕은 복사가 아닌 깊은 복사로 처리하는 것이다.

대체 깊은 복사와 얕은 복사가 무엇이길래 이런 문제가 발생한 걸까?

무엇인지 아래에서 확인해보자!

얕은 복사

  • 객체의 참조 값(주소)을 복사
  • 참조 값을 복사했기 때문에 같은 객체를 바라봄

아래 예제 코드를 보면

Student class가 있고 b 인스턴스 변수가 a 인스턴스 변수를 복사한다.

이 때 얕은 복사가 이루어지는데 위에 설명했던 대로 객체의 참조 값만 복사 되기 때문에

a 변수가 가지고 있던 Student 객체의 주소 값을 b가 가지게 된다.

이로 인해

a 와 b 둘 다 Heap영역에 있는 동일한 객체를 바라보게 되고 b 값을 수정하면 a의 값도 변하게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Student
{
   public string _name;
   public int _age;
}

class Program
{
    static void Main(string[] args)
    {
        Student a = new Student
        {
            _name = "Night",
            _age = 27
        };

        // a를 b에 얕은 복사
        Student b = a;

        // b 값을 수정
        b._name = "Owl";
        b._age = 24;

        Console.WriteLine($"A 학생 이름: {a._name}, 나이: {a._age}");
        Console.WriteLine($"B 학생 이름: {b._name}, 나이: {b._age}");
    }
}
1
2
3
출력 결과
A 학생 이름: Owl, 나이: 24
B 학생 이름: Owl, 나이: 24

cs_04

왜 이런 일이 발생할까??

C#은 값 형식참조 형식 두 형식으로 나누어져 있다고 볼 수 있다.

참조 형식인 class는

1
2
3
4
5
Student a = new Student
        {
            _name = "Night",
            _age = 27
        };

위와 같이 객체를 생성할 때 Heap에 생성한 객체의 주소 값을 가지게 되는데(이를 “객체를 참조했다.” 라고 한다.)

이 때 Student b = a; 를 통해 a의 값을 복사하면 b는 a가 가지고 있던 객체의 주소 값을 가져오게 된다.

그러므로, b와 a는 동일한 객체를 바라보게 되는 거고 b의 값을 바꾸면 자연스럽게 a의 값도 바뀌게 되는 것이다.

1
2
class 에서 값 형식인 struct 로 변경해서 비교해본다면
더 쉽게 이해할 수 있다.

깊은 복사

  • 객체의 실제 값을 복사
  • Heap 영역에 새로운 메모리 공간을 생성해 새로운 객체를 생성

코드를 보면 Clone() 또는 DeepCopy() 메소드를 통해 새로운 객체를 생성해서 넘겨주고 있다.

두 메소드는 동일하게 객체의 실제 값 즉, 변수들의 값을 복사해 새로운 객체를 반환하고 있다.

이렇게 반환한 객체는 Heap 영역에 새로운 메모리 공간을 차지하게 되고 a 와 b는 서로 다른 객체를 바라보게 된다.

그 결과,

1
2
b._name = "Owl";
b._age = 24;

위의 코드로 b의 값을 변경하더라도 a의 값에는 영향을 주지 않게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Student : ICloneable
{
   public string _name;
   public int _age;

    /// <summary>
    /// MSDN 인터페이스에 맞추어 깊은 복사
    /// </summary>
    /// <returns></returns>
    public object Clone() => new Student
    {
        _name = this._name,
        _age = this._age
    };

    /// <summary>
    /// 깊은 복사
    /// </summary>
    /// <returns></returns>
    public Student DeepCopy() => new Student
    {
        _name = this._name,
        _age = this._age
    };
}

class Program
{
    static void Main(string[] args)
    {
        Student a = new Student
        {
            _name = "Night",
            _age = 27
        };

        Student b = a.Clone() as Student;   // MSDN 인터페이스에 맞춘 깊은 복사
        // Student b = a.DeepCopy();        // 깊은 복사

        b._name = "Owl";
        b._age = 24;

        Console.WriteLine($"A 학생 이름: {a._name}, 나이: {a._age}");
        Console.WriteLine($"B 학생 이름: {b._name}, 나이: {b._age}");
    }
}
1
2
3
출력 결과
A 학생 이름: Night, 나이: 27
B 학생 이름: Owl, 나이: 24

cs_05

결론

위 예제 코드를 보면 둘 다 코드적으로는 문제가 없는 코드이다.

하지만 결과에는 큰 차이가 있다.

얕은 복사와 깊은 복사는 학교에서도 배우는 만큼 중요하고 기본적인 내용이다.

실무에서 이 내용을 이슈로 경험을 해보니 역시 기본기가 가장 중요하다 라는 것을 다시 한번 깨닫게 되었다.

This post is licensed under CC BY 4.0 by the author.