얕은 복사 (shallow copy) / 깊은 복사 (deep copy)
프로그램의 동작 중 수 많은 데이터의 복사가 일어납니다.
기본 자료형을 복사할때는 별다른 문제가 없지만, 구조체나 클래스를 복사할 때 반드시 주의해야 할 점이 있습니다.
class Number
{
private:
int* data;
public :
Number(int val)
{
data = new int;
*data = val;
};
void setValue(int val)
{
*data = val;
};
int getValue() { return *data; };
};
int main(int argc, char** argv)
{
Number num(3);
Number copyNum = num;
printf("%d %d\n",
num.getValue(),
copyNum.getValue());
copyNum.setValue(5);
printf("%d %d\n",
num.getValue(),
copyNum.getValue());
return 0;
}
3 3
5 5
위의 코드는 얕은 복사의 전형적인 코드입니다.
내부적으로 데이터를 포인터를 이용해 관리하고 있으며, Number 라는 클래스를 복사하면 멤버변수의 포인터 값만 그대로 복사됩니다.
따라서 복사된 클래스 쪽의 값을 바꾸면 원본까지 같이 바뀌는 현상이 나타납니다.
깊은 복사는 복사할 때 내부의 내용까지 같이 복사하는 기법입니다.
이를 구현하기 위해 복사 생성자를 사용할 수 있습니다.
얕은 복사와 깊은 복사는 각각 장단점이 있기 때문에 필요에 따라 사용합니다.
복사 생성자 (Copy constructor)
Number(Number& rhs)
{
data = new int;
*data = *rhs.data;
};
복사 생성자는 반환형이 없으며, 인수를 자기 자신 자료형의 l-value 레퍼런스를 받는 멤버함수 입니다.
위의 코드를 얕은 복사 예제 코드의 Number 클래스에 추가하면 깊은 복사가 이루어집니다.
Number copyNum = num;
Number copyNum2(num);
Number* copyNum3 = new Number(num);
복사 생성자는 하나의 인스턴스가 복사되어 생성될 때 호출됩니다.
- 예제의 첫번째 라인과 같은, 생성과 동시에 초기화 대입을 하는 경우 복사 생성자가 호출됩니다.
Number copyNum(1);
copyNum = num;
그러나, 위의 경우는 복사생성자가 아닌 대입 연산자로 취급됩니다.
깊은 복사를 완전히 구성하려면 복사 생성자 뿐만 아니라, 연산자 오버로딩을 통해 대입 연산자도 재 구성할 필요가 있습니다.
- 복사 생성자는 기본 복사 생성자를 완전히 대체합니다.
Number(Number& rhs)
{
};
위와 같은 코드를 작성하면 기본 복사(얕은 복사)도 일어나지 않습니다.
복사된 결과물은 아무런 데이터도 없는 상태로, getValue 나 setValue 멤버 함수 호출시 포인터 참조 에러가 발생하게 됩니다. 따라서 복사 생성자를 사용할 경우, 복사가 필요한 모든 멤버를 명시적으로 복사해 줄 필요가 있습니다.