삽질의 현장/- .NET

#047_닷넷(.NET)_.Net Framework 기본 - 구조체와 클래스에서 복사 (Shallow ,Deep Copy)

shovelman 2015. 10. 30. 16:35


안녕하세요 삽잡이입니다.


이번 시간에는 struct, class의 경우를 비교해보며

각 참조를 가졌을 때, 값을 가졌을 때 어떻게 복사가 되어가는지 알아보겠습니다.

하... 힘든 시간이 될 수 있지만 힘차게 달려가봅시다!


필자는 Point와 Rect라는 형식을 가지고 비교를 진행해보도록 하겠습니다.



시작 전에 복사의 진리(?)를 다시 한번 곱씹어보고 가시겠습니다...

값 형식과 참조 형식이 뭡니까?

값 형식은 왜 값 형식이라고 부를까요?

변수 자체가 값이고 모든 복사가 값으로 되기 때문입니다.

참조형식은 그렇다면?

객체 자체가 참조입니다. 객체는 따로 있고 참조자를 통해 가리킬 뿐이지요.

참조형식일 경우 복사할 때 참조가 복사되니 참조 복사라고 부를 수 있습니다.


본격적으로 시작해볼까요?



Case 1. Class 안에 Struct가 있다면?


캬... 뭔가 책같지 않나요? 뭔가 간지가 난다...

사실 지금 마시고 있는 딸기 바나나 쉐이크가 이상한거 같습니다. 저도 이상해지고 있습니다


아... 이게 아닙니다... 시작해보도록 하지요...


 자... Class Rect 안에 Struct Point가 정의되어있습니다.



이 경우에는 참조 복사입니다.

rt가 가리키고 있는 객체를 rt2도 가리키게 되지요.



깊고 얕음을 따질 필요가 없습니다.

참조 자체를 복사하기 때문이지요...

객체를 복사할 때만 얕은, 깊은 복사를 나눌 수 있다는 사실을 잊지마시길 바랍니다.

(이쯤되면 못 잊습니다...)


물론, 구조체 pt가 Rect 객체에 복사될 때에는 얕은 복사가 일어납니다.

Point 객체 내에 참조자가 없기 때문에 얕은 복사가 일어나는 것이지요...


그렇다면 객체 자체를 복사하고 싶다면 어떻게 해야할까요?

C++과 달리 C#에는 복사 생성자가 없습니다.

또한 생성자에 복사 과정을 넣어도 딱따구리(?)가 될 뿐입니다...

왜냐하면 C#에서는 MemberwiseClone()이라는 메서드를 제공해주기때문입니다.


MemberwiseClone() 메서드는 

자기와 똑같은 객체를 만들어내는데 '멤버 대 멤버'복사를 해줍니다.

return 타입은 object 형식이기 때문에 형식에 맞게 반환만 해주면 되지요.


따라서 복사를 할 경우 이처럼 할 수 있습니다.



'멤버들을 가지고 복사하자!' 이런 뜻입니다.

자신과 똑같은 복제본을 만들어 내는 것인데, 멤버 대 멤버로 만들겠다 이겁니다.


물론, 복사할 객체가 참조형이 아니라면 해당 메서드를 쓸 필요가 없습니다.



 

 /*

 사실 MemberwiseClone() 메서드는 private 속성이 있기 때문에,

 호출을 할 수 없고 위임을 해줘야합니다.

 MemberwiseClone() 메서드를 사용하는 코드는 잠시 후 아래에서 보여드리겠습니다.

 지금은 단지 해당 메서드의 필요성만을 느끼시길 바랍니다!

 */




 

만약, 위에 쓴 것처럼 MemberwiseClone() 메서드를 사용하면 

이것은 고로 미친짓이지요...

pt 자체가 객체이니 그냥 값 복사만 하면 되지요...


객체를 복사하기 위해 MemberwiseClone() 메서드를 사용하는 것입니다.


자... 그럼 다시 한번 살펴보도록 하겠습니다...

우리는 Class 안에 Struct형태의 Point형식이 포함되있는 객체를 복사하려고합니다.

이 때 참조를 가지고 있지 않기 때문에 얕은 복사를 해도 문제가 없게 됩니다.








Case 2. Struct 안에 Struct가 있다면?




Struct라는 사실은 객체가 Stack 영역에 만들어진다는 것을 의미합니다.



 

여기서 rt는 변수 자체가 객체입니다.

참조를 가지고 있지 않습니다.

별로 헷갈릴 것이 없지요...


객체가 복사를 하려고 할 때

다시 말씀드리지만 MemberwiseClone() 메서드는 절대 적합하지 않습니다.



그렇다면 이제 Rect가 Struct Point가 Class인경우를 살펴볼까요?


Case 3. Struct 안에 Class가 있다면?




Rect형 객체를 만들 때 참조를 가지게 됩니다.

Rect는 struct형태입니다.

따라서 만드는 순간 stack에 저장이 됩니다.

pt는 참조자입니다. Point 객체를 가리키고 있다 이 소리입니다.


그런데, 복사를 하게될 경우 참조를 복사하게 되지 않습니까?

이 경우는 멤버 대 멤버를 복사하는 경우에 해당하지 않습니다.

rt2를 rt의 내용 그대로 복사하게되면 얕은 복사가 되기 때문에 똑같은 내부 참조를 하게 됩니다.

즉, 객체 내부에 참조가 있다면 얕은 복사(Shallow Copy)를 할 수 없다 이겁니다.




하지만, 위의 그림은 지금 상태로는

객체에서 객체를 복사하기 때문에 Shallow Copy입니다.


각 stack에 있는 객체들이 각각의 참조를 하기 위해서는 어떻게 해야할까요?


잠시후에 살펴보도록 하겠습니다...


Case 4. Class 안에 Class가 있다면?



Class형 Rect안에 Class Point를 가지고 있을 경우입니다...



Rect rt2를 r로 그냥 복사할 경우 참조 복사가 될 뿐입니다.

하지만, 해당 메서드를 사용하게 되면 객체를 복사하게 됩니다.




객체가 복사가 될 지라도 객체 내부에 있는 참조는 

같은 Point 객체를 가리키고 있습니다...

즉, 둘 다 참조를 가지고 있을 경우에는 

복사가 제대로 이루어지고 있지 않는 사실을 알게 됬습니다.


복사하고자 하는 객체가 값 멤버일 때는 상관이 없지만,

복사하고자하는 객체가 참조 멤버를 가질 경우에는 좀 골치가 아프군요..



New 1. Hope Deep Copy!


값을 복사할 때에는 상관이 없었지만, 참조가 있을 경우 골치가 아픕니다...


우리가 진정 원하는 복사는 이러한 그림인데 말입니다...

 





참조가 있으면 기본적인 복사로는 안됩니다.

그래서 C++에서는 복사 함수를 오버라이드하지요..

C#에서는 MemberwiseClone()메서드로는 안됩니다.

따라서 우리가 직접 만들어줘야합니다.



New 2. Struct 내부에 참조를 가지고 있을 경우


우선, 깊은 복사를 시도할 구조체 안에 참조형식이 있을 경우를 살펴보겠습니다.



우리가 직접 만들어봅시다..



이처럼, 자기 자신의 복사본(Rect)을 반환하는 public 함수를 하나 만듭니다.

일반적으로 Clone()이라는 명칭을 붙입니다.


이처럼 구현하게 되면,

반환된 Rect 객체는 새로운 Point를 할당한 참조를 가지게 됩니다.



그러면 우리는 꿈에 그리는 깊은 복사를 성공하게 된 것입니다.

자... 이제 우리는 이처럼 깊은 복사를 할 수 있습니다.



깊은 복사를 하게 되다니 너무도 기쁩니다!



New 3. Class 내부에 참조를 가지고 있을 경우


이번에는 깊은 복사를 수행할 Class 내부에 참조를 가지고 있을 경우를 살펴보겠습니다.



이번에도 역시 Clone() 메서드를 구현해보도록 하겠습니다.



나머지는 똑같습니다. class로만 바꿨을 분입니다.



rt가 clone() 메서드를 호출하면 해당 메서드가 호출되어 새로운 Rect 객체가 생성됩니다.

구조체였을 때에는 new 키워드를 통해 생성자를 호출했지만,

지금과 같은 클래스일 경우에는 생성하고자, 

새로운 참조를 갖게 하고자 구현된 샘이 됩니다.


추가적으로 참조에서 많이 사용하는 코드로 한번 바꿔보도록 하지요...



MemberwiseClone() 메서드는 private이기 때문에

사실, 외부에서 호출은 안되고 위임해줘야합니다.

그리고 값이 같도록 pt에 새롭게 생성을 해주고 return을 해주면 됩니다.


이전에 보여드린 코드가 더 쉽나요?

하지만, 멤버가 많을 경우에는 이게 더 쉬울껄요??


사실 우리는 이렇게 사용할 필요는 없습니다...

하지만, 닷넷에서 제공해주는 ICloneable 인터페이스를 사용하기 위해

기초를 차근 차근 밟아본 것입니다...


흐름을 끊기 싫어 이번에는 좀 (원래도 길지만..) 길게 뽑아봤습니다.


다음시간에는 ICloneable 인터페이스로 찾아뵙도록 하겠습니다.


이상 삽잡이였습니다!