깊은 복사와 얕은 복사

객체는 다른 객체를 참조할 수 있는데 이럴 경우에 깊은 복사인가, 얕은 복사인가에 따라 결과값이 달라질 수 있으니 주의해야합니다.

복사를 한다는 것은 기존 객체와 같은 값을 가진 새로운 객체로 만든다는 것

객체들은 멤버를 가지고 있고 그 멤버들은 값일 수도 있고 참조 형식일 수도 있습니다.

 

- 바로 이 객체들이 가진 값 형식과 참조 형식의 복제 방식에 따라 얕은 복사와 깊은 복사의 개념이 나누어지게 됩니다.

 

얕은 복사(Shallow Copy)

얕은 복사는 객체가 가진 멤버들의 값을 새로운 객체로 복사하는데 만약 객체가 참조타입의 멤버를 가지고 있다면 참조값만 복사가 됩니다.

 

얕은 복사 코드

#include <iostream>
#include <memory>

class TestClass
{
public:
	char* m_Name;
	int m_Age;

	void PrintInfo()
	{
		std::cout << "Name : " << m_Name << std::endl;
		std::cout << "Age  : " << m_Age << std::endl;
	}

	TestClass()
	{

	}
	~TestClass()
	{
		std::cout << "소멸자 호출" << std::endl;
	}
};

int main()
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
	TestClass A_Class;
	A_Class.m_Name = new char[3];
	strcpy_s(A_Class.m_Name, 3, "YG");
	A_Class.m_Age = 10;

	A_Class.PrintInfo();

	std::cout << "B클래스 = A클래스 하고 A클래스 정보를 바꾸면?" << std::endl;

	TestClass B_Class = A_Class;

	strcpy_s(A_Class.m_Name, 3, "YJ");
	A_Class.m_Age = 20;

	A_Class.PrintInfo();
	B_Class.PrintInfo();
}

결과

A클래스의 이름을 YJ 나이를 20으로 변경했을때
A클래스를 얕은복사한 B클래스의 이름까지 바뀐걸 볼수있습니다.
기본 복사 생성자를 이용했으니 이번엔 얕은 복사 생성자 코드.

#include <iostream>
#include <memory>

class TestClass
{
public:
	char* m_Name;
	int m_Age;

	void PrintInfo()
	{
		std::cout << "Name : " << m_Name << std::endl;
		std::cout << "Age  : " << m_Age << std::endl;
	}

	TestClass()
	{
		std::cout << "생성자 호출" << std::endl;
	}

//////////////////////얕은복사
	TestClass(const TestClass& _Value)
	{
		std::cout << "복사 생성자 호출" << std::endl;
		m_Age = _Value.m_Age;
		m_Name = _Value.m_Name;
	}

	~TestClass()
	{
		std::cout << "소멸자 호출" << std::endl;
	}
};

int main()
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
	TestClass A_Class;
	A_Class.m_Name = new char[3];
	strcpy_s(A_Class.m_Name, 3, "YG");
	A_Class.m_Age = 10;

	A_Class.PrintInfo();

	TestClass B_Class = A_Class;

	std::cout << "B클래스 = A클래스 하고 A클래스 정보를 바꾸면?" << std::endl;


	strcpy_s(A_Class.m_Name, 3, "YJ");
	A_Class.m_Age = 20;

	A_Class.PrintInfo();
	B_Class.PrintInfo();
}

 

빌드시 복사 생성자에 중단점이 걸린걸 확인 할 수 있습니다.

디폴트 복사 생성자는 얕은 복사를 사용하는걸로 확인됩니다.
이런 방식에 문제점이 멀까요

	TestClass A_Class;
	A_Class.m_Name = new char[3];
	strcpy_s(A_Class.m_Name, 3, "YG");

new char[3] 동적 할당을 하고있어서 릭이 체크되고 있는데
TestClass는 A_Class, B_Class 두개를 만들었는데  3 bytes long. 만큼만 체크가 되고있습니다.

소멸자에서 릭을 delete 해보겠습니다.

	~TestClass()
	{
		std::cout << "소멸자 호출" << std::endl;
		delete[] m_Name;
	}

펑 하고 터졌습니다
TestClass는 A_Class, B_Class 두개를 만들었는데 얕은 복사를 해서 메모리 참조를 하고있으니
삭제한 메모리를 한번더 삭제하려고 하니까 터지고있습니다. 그럼 깊은 복사 생성자를 만들어 보겠습니다.


깊은 복사(Deep Copy)

깊은 복사 또는 전체 복사.
얕은 복사와는 달리 객체가 가진 모든 멤버(값과 참조형식 모두)를 복사하는 것을 말합니다.
객체가 참조 타입의 멤버를 포함할 경우 참조값의 복사가 아닌 참조된 객체 자체가 복사되는 것을 깊은 복사라 합니다.

깊은 복사 코드

#include <iostream>
#include <memory>

class TestClass
{
public:
	char* m_Name;
	int m_Age;

	void PrintInfo()
	{
		std::cout << "Name : " << m_Name << std::endl;
		std::cout << "Age  : " << m_Age << std::endl;
	}

	TestClass()
	{
		std::cout << "생성자 호출" << std::endl;
	}

	/////////////////깊은 복사로 바꿈
	TestClass(const TestClass& _Value)
	{
		std::cout << "복사 생성자 호출" << std::endl;
		m_Age = _Value.m_Age;
		m_Name = new char[strlen(_Value.m_Name) + 1];
		strcpy_s(m_Name, strlen(_Value.m_Name) + 1, _Value.m_Name);
	}

	~TestClass()
	{
		std::cout << "소멸자 호출" << std::endl;
		delete[] m_Name;
	}
};

int main()
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
	TestClass A_Class;
	A_Class.m_Name = new char[3];
	strcpy_s(A_Class.m_Name, 3, "YG");
	A_Class.m_Age = 10;

	A_Class.PrintInfo();

	TestClass B_Class = A_Class;

	std::cout << "B클래스 = A클래스 하고 A클래스 정보를 바꾸면?" << std::endl;


	strcpy_s(A_Class.m_Name, 3, "YJ");
	A_Class.m_Age = 20;

	A_Class.PrintInfo();
	B_Class.PrintInfo();
}

 

결과

A클래스의 이름만 수정되고!
B클래스의 이름은 그대로!
릭도 남지않고 깔끔하게 프로그램이 종료됬습니다!.

'게임 프로그래밍 > C++ 기초' 카테고리의 다른 글

C++ 바이트 패딩(Byte Padding)  (0) 2020.03.09
C++ #define 매크로 상수, 매크로 함수  (0) 2020.03.08
std::weak_ptr  (0) 2020.03.03
std::shared_ptr 정의 및 문제점  (0) 2020.03.02
C++ new, malloc  (0) 2020.03.02

+ Recent posts