c++/복사 생성자

'깊은 복사'와 '얕은 복사'

TIM_0529 2023. 1. 3. 05:46
반응형

이 전 글에 디폴트 복사 생성자에 대해서 이야기했다. 멤버 대 멤버에 의한 복사로 '얕은 복사 (shallow copy)라고 한다.

이는 멤버변수가 힙의 메모리 공간을 참조하는 경우에 문제가 됩니다. 

얕은 복사에 문제점에 대해서 먼저 이야기 하겠습니다.

 

#include <iostream>
#include <cstring>
#pragma warning(disable:4996)
using namespace std;

class Person {
	char* name;
	int age;

public:
	/*Person()
	{
		name = NULL;
		age = 0;
		cout << "Called Empty Person Constructor" << endl;
	}*/
	Person(char* my_name, int my_age)
	{
		int len = strlen(my_name) + 1;
		name = new char[len];
		strcpy(name, my_name);
		age = my_age;
	}
	void SetPersonInfo(char* my_name, int my_age)
	{
		name = my_name;
		age = my_age;
	}
	void ShowPersonInfo()
	{
		cout << "Name: " << name << endl;
		cout << "Age: " << age << endl;
	}
	~Person()
	{
		delete[]name;
		cout << "Called destructor" << endl;
	}
};

int main()
{
	Person man1("SDLkfj",24);
	Person man2 = man1;
	man1.ShowPersonInfo();
	man2.ShowPersonInfo();
}

Person 객체가 2개가 생성이 되었고 man1은 Person에 명시적으로 선언된 생성자에 의해 초기화가 되었습니다. 

man2는 디폴트 복사 생성자에 의해 암시적으로 선언된 생성자에 의해 man1을 대입해서 객체가 생성이 되었습니다.

 

컴파일러의 종류와 설정에 따라 다를 순 있지만 다음을 실행하면, 소멸자가 한 번만 실행이 되는 것을 알 수 있습니다.

이것이 얕은 복사에 문제점입니다.

 

man2는 생성 시 man1에 name이라는 멤버변수에 주소를 저장합니다 그리고 그에 따른 포인터 값으로는 SDLfj가 있습니다.

man2의 소멸자가 먼저 호출이 되고 소멸자로 인해 참고하고 있던 문자열은 없어지게 됩니다. 그다음 man1의 소멸자가 문자열을 삭제하기 위해 진입하면 에러가 발생합니다. 왜냐하면 man2에 소멸자에 의해 이미 삭제가 된 문자열을 참조하므로 컴파일 에러 혹은 종료가 됩니다.

 

다음은 깊은 복사를 통해 얕은 복사에 문제를 보완한 코드입니다.

#include <iostream>
#include <cstring>
#pragma warning(disable:4996)
using namespace std;

enum LEVEL
{
	CLERK, SENIOR, ASSIST, MANAGER
};

class NameCard 
{
	char* name;
	char* compeny_name;
	char* phone_number;
	int level;

public:
	NameCard(const char* name, const char* compney_name, const char* phone_number, int level): level(level)
	{
		this->name = new char[strlen(name) + 1];
		this->compeny_name = new char[strlen(compney_name) + 1];
		this->phone_number = new char[strlen(phone_number) + 1];
		strcpy(this->name, name);
		strcpy(this->compeny_name, compney_name);
		strcpy(this->phone_number, phone_number);
	}
	NameCard(NameCard& copy) :level(copy.level)
	{
		this->name = new char[strlen(copy.name) + 1];
		this->compeny_name = new char[strlen(copy.compeny_name) + 1];
		this->phone_number = new char[strlen(copy.phone_number) + 1];
		strcpy(this->name, copy.name);
		strcpy(this->compeny_name, copy.compeny_name);
		strcpy(this->phone_number, copy.phone_number);
	}
	void ShowCardInfo()
	{
		cout << "Name : " << name << endl;
		cout << "Compeny Name : " << compeny_name<< endl;
		cout << "P.H : " << phone_number<< endl<<endl;
	}
	~NameCard()
	{
		delete[]name;
		delete[]compeny_name;
		delete[]phone_number;
	}
};

int main()
{
	NameCard manClerk("Lee", "ABCEng", "010-1111-2222", LEVEL::CLERK);
	NameCard manSENIOR("HONG", "ABCEng", "010-1111-2222", LEVEL::CLERK);
	NameCard manASsist("KIM", "ABCEng", "010-1111-2222", LEVEL::CLERK);
	NameCard man2Clerk = manClerk;
	manClerk.ShowCardInfo();
	manSENIOR.ShowCardInfo();
	manASsist.ShowCardInfo();

}

깊은 복사 생성 시 포인터 주소 참조 값까지 복사를 해서 깊게 복사 라고 합니다.

 

 

 

man2 가 새로 생성시 man1에 멤버 변수를 할당받는 건 얕은 복사와 똑같지만, 그 값만 참조하는 것이 아닌 새로운 주소를 할당받고 그 주소 안에 문자열을 저장하여 같은 문자열을 갖고 있지만 독립적인 주소로 값을 저장하여 소멸자가 할당받은 객체에 문자열을 삭제하더라도 다른 객체에 영향을 주지 않습니다.

 

반응형

'c++ > 복사 생성자' 카테고리의 다른 글

[C++] 복사 생성자의 호출 시점  (0) 2023.01.04
[C++] 디폴트 복사 생성자  (0) 2023.01.03