
디폴트 복사 생성자는 멤버 대 멤버의 복사를 진행한다. 이러한 방식의 복사를 '얕은 복사'라고 하는데, 이는 멤버변수가 힙의 메모리 공간을 참조하는 경우에 문제가 된다.
디폴트 복사 생성자의 문제점
다음 예제를 통해 문제를 확인해보자.
ShallowCopyError.cpp
#include <iostream>
#include <cstring>
using namespace std;
class Person
{
char * name;
int age;
public:
Person(char * myname, int myage)
{
int len=strlen(myname)+1;
name=new char[len];
strcpy(name, myname);
age=myage;
}
void ShowPersonInfo() const
{
cout<<"이름: "<<name<<endl;
cout<<"나이: "<<age<<endl;
}
~Person()
{
delete []name;
cout<<"called destructor!"<<endl;
}
};
int main(void)
{
Person man1("Lee dong woo", 29);
Person man2=man1;
man1.ShowPersonInfo();
man2.ShowPersonInfo();
return 0;
}
이름: Lee dong woo
나이: 29
이름: Lee dong woo
나이: 29
called destructor!
실행 결과를 보면, 문자열 called destructor! 가 단 한번만 출력되었다. 분명 두 개의 객체를 생성했기에 소멸자도 두 번 호출되어야 할 텐데 말이다.
문제점은 바로
Person man2=man1;
이 부분에서 진행되는 얕은 복사에 있다.
이 문장에 의해 man2가 생성되면서 디폴트 복사 생성자가 호출된다.
그런데 디폴트 복사 생성자는 멤버 대 멤버를 단순히 복사만 하므로, 다음의 구조를 띈다.

디폴트 복사 생성자는 멤버 대 멤버의 단순 복사를 진행하기 때문에 복사의 결과로 하나의 문자열을 두 개의 객체가 동시에 참조하는 꼴을 만든다. 이로 인해 객체의 소멸과정에서 문제가 발생한다.
그럼 어떠한 문제가 발생하는가? 위의 그림의 생항에서 man2 객체가 먼저 소멸된 상황을 가정해보자. 그럼 man2의 소멸자가 호출되면서 다음 문장도 실행이 된다.
delete []name;
즉, 객체 man2의 소멸로 인해 man1 객체의 멤버 name이 참조하는 문자열은 이미 소멸되어 delete 연산을 하는 데에 문제가 생긴다. 따라서 '깊은 복사'를 이용해 복사 생성자를 정의해야 한다.
‘깊은 복사’를 위한 복사 생성자의 정의
'깊은 복사'는 멤버뿐만 아니라, 포인터로 참조하는 대상까지 깊게 복사한다.

깊은 복사를 구성하는 복사 생성자이다.
멤버 변수 age의 멤버 대 멤버 복사가 이루어 진다.
메모리 공간 동적할당 후, 문자열 복사, 그리고 할당된 메모리 주소 값을 멤버 name에 저장한다.