본문 바로가기

교육 노트/C# 강의

[C# 때려잡기] C# 강의 22. 복사 생성자와 깊은 복사

이전 강의에서 생성자를 배웠다.

특히 생성자에서 파라미터를 받아 객체를 생성하면서 각종 초기화 과정을 진행 해 줄 수 있었는데

생성자에서는 복사생성자라는 특수한 생성자가 하나 존재한다.


1. 복사 생성자

자기 자신과같은 형태의 객체를 인자로 받는 생성자를 의미한다.


public Character(Character c)
{

}

복사 생성자는 같은 클래스를 인자로 받는 생성자이며 대부분 인자로 받은 클래스의 내용을 카피하는데 사용하기 때문에 복사생성자라고 한다.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp3
{
class Character
{
private int speed;

public int Speed
{
get { return speed; }
set { speed = value; }
}
private int hp;

public int HP
{
get { return hp; }
set { hp = value; }
}


public Character(int hp, int speed)
{
this.hp = hp;
this.speed = speed;
}

public Character(Character c)
{
this.hp = c.hp;
this.speed = c.speed;
}


}
class Program
{
static void Main(string[] args)
{
Character mario = new Character(50,19);
Character luigi = new Character(mario);

Console.WriteLine(luigi.HP);
Console.WriteLine(luigi.Speed);

}
}
}

과 같이 사용할수있다.



루이지는 마리오를 파라미터로 받아

마리오와 같은hp 로 자신을 초기화했다


또한 복사생성자에서 받는 파라미터인 c 의 클래스가 Chararcter이다.

따라서 같은 클래스 이므로 c.hp 와 같이 Character의 private 변수에 접근이 가능하다.




그럼 여기서 복사생성자가 없으면 어떻게 될까?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp3
{
class Character
{
private int speed;

public int Speed
{
get { return speed; }
set { speed = value; }
}
private int hp;

public int HP
{
get { return hp; }
set { hp = value; }
}


public Character(int hp, int speed)
{
this.hp = hp;
this.speed = speed;
}

//public Character(Character c)
//{
// this.hp = c.hp;
// this.speed = c.speed;
//}


}
class Program
{
static void Main(string[] args)
{
Character mario = new Character(50,19);
Character luigi = new Character(mario);

Console.WriteLine(luigi.HP);
Console.WriteLine(luigi.Speed);

}
}
}




안된다.

아니 없으면당연히 안되는거 아닌가요.. 라고 생각할수도 있지만.
C++에서는 복사생성자를 만들지 많으면 자동으로 얕은 복사를 하는 복사생성자가 만들어지는데
C#에서는 만들어주지않으면 동작하지 않는다.



2. 얕은 복사와 깊은 복사 (shallow copy, deep copy)

클래스 내부에 변수로 배열과 같은 참조형 타입이 있다고 해보자

마리오 객체의 포인터는 동적할당으로 A라고 저장된 배열을 가르키고 있다.

루이지 객체는 마리오 객체를 카피 할것이다.

복사가 끝나면 우리가 원하는 상황은 위 그림과 같을것이다.


루이지의 배열을 바꾸면

이런식으로 작동하기를 원할것이다.


그러나 중요한점은 p가 참조형 이라는것이고 넘어갈때 참조형으로 넘어가게된다..

즉 단순히 복사를 하게 되면 복사 생성자는 해당 값조차 그냥 복사해버릴 테고

그렇게 되면


이렇게 복사가 되버릴것이다!

참조형이기때문에 같은값을 참조하게 되는것이다.

여기서 루이지에서 값을 바꿔버리면


망할 루이지에서 바꿧는데 마리오까지 바뀌어버렸다


여기서 마리오 객체가 이제 쓸모가 없어져서 지워지면

마리오가 지워지면서 배열값을 지워버린다면 이렇게 될것이다.


이러한 복사방법이 얕은복사 (shallow copy) 이다

그냥 주소값이든 참조형이든 그냥 값이든 1대1로 고대로 복사하는것이 얕은복사인데

그냥 =으로 넣어버린다면 이런 얕은 복사를 수행하게 된다.


우리가 원래 원했던 복사방식이 깊은 복사 (deep copy) 로 참조형이라면 참조하고있는 값을 추적하여 해당 값을 복사하여 할당하고 이 값을 내 변수가 참도하도록하는 방식이다.

해당방식은 기본으로 제공해주지 않기때문에

개발자가 스스로 프로그래밍을 해주어야한다.

그래서 우리가 직접 복사생성자를 만들수 있도록 한것이다.


예시로 살펴보자


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp3
{
class Character
{
private int speed;

public int Speed
{
get { return speed; }
set { speed = value; }
}
private int hp;

public int HP
{
get { return hp; }
set { hp = value; }
}

private int[] itemList;

public int[] ItemList
{
get { return itemList; }
set { itemList = value; }
}



public Character(int hp, int speed)
{
this.hp = hp;
this.speed = speed;
itemList = new int[3];
itemList[0] = 10;
}

public Character(Character c)
{
this.hp = c.hp;
this.speed = c.speed;
this.itemList = c.itemList;
}


}
class Program
{
static void Main(string[] args)
{
Character mario = new Character(50,19);
Character luigi = new Character(mario);

mario.ItemList[0] = 100;
Console.WriteLine(luigi.ItemList[0]);

}
}
}

마리오의 ItemList[0]을 바꾸어도 루이지의 ItemList[0]가 바뀌었다.



딥카피를 하면 다음과 같다

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp3
{
class Character
{
private int speed;

public int Speed
{
get { return speed; }
set { speed = value; }
}
private int hp;

public int HP
{
get { return hp; }
set { hp = value; }
}

private int[] itemList;

public int[] ItemList
{
get { return itemList; }
set { itemList = value; }
}



public Character(int hp, int speed)
{
this.hp = hp;
this.speed = speed;
itemList = new int[3];
itemList[0] = 10;
}

public Character(Character c)
{
this.hp = c.hp;
this.speed = c.speed;
this.itemList = c.itemList.Clone() as int[];
}


}
class Program
{
static void Main(string[] args)
{
Character mario = new Character(50,19);
Character luigi = new Character(mario);

mario.ItemList[0] = 100;
Console.WriteLine(luigi.ItemList[0]);

}
}
}



728x90