프로그래머/CPP_강의정리

cpp 얕은 복사와 깊은 복사

미역국마싯 2021. 9. 23. 18:49

포인터 멤버 변수 사용 예시

복사를 알아보기 전에 포인터를 멤버 변수로 가지는 경우를 살펴본다.

class Pet
{

};

class RabbitPet : public Pet
{

};

class Knight
{
public:
	Knight()
	{
		// Knight 와 Pet 의 생명 주기를 동일하게 한다.
		_pet = new Pet();
	}

	~Knight()
	{
		delete _pet;
	}
public:
	int _hp = 100;
	Pet* _pet;
};

Knight 클래스에 있는 멤버 변수 _pet을 왜 포인터로 설정했냐면, Pet 클래스를 상속받는 자식 클래스도 담고 싶고 Knight 클래스와 독립적인 객체임을 표현하고 싶기 때문이다.

  1. 기본 멤버 변수로 _pet을 설정하면
    1. Knight 객체가 사라지면, Knight 객체가 가지고 있던 pet도 사라진다.
    2. RabbitPet 클래스와 Pet 클래스를 따로 멤버 변수로 넣어야 한다. 즉, Pet의 자식 클래스가 많아질수록 비효율적으로 관리해야 한다.
  2. 포인터 멤버 변수로 _pet을 설정하면
    1. Knight 객체가 사라져도 _pet이 가리키는 주소에 있는 원본 pet 객체는 사라지지 않는다.
    2. Pet의 모든 자식 클래스를 _pet이 받아들일 수 있다.

 

 

얕은 복사(Shallow Copy)

얕은 복사는 멤버 데이터를 비트열 단위로 똑같이 복사하는 것을 말한다.

쉽게 말해 메모리 영역 값을 그대로 복사하는 것이다.

int main()
{
	Pet* pet = new Pet();
	Knight knight;
	knight._pet = pet;

	// knight를 복사하는 방법
	// 복사 생성자
	Knight knight2 = knight;	
	Knight knight3(knight);

	// 복사 대입 연산자
	Knight knight4;
	knight4 = knight;	

	return 0;
}

knight 객체를 복사하는 방법은 2가지가 있다. 복사 생성자나 복사 대입 연산자를 이용하는 것이다. 공통점은 둘 다 정의해주지 않으면 컴파일러가 자동으로 만들어준다는 점과 knight가 가진 메모리 영역을 똑같이 복사한다는 것이다. 즉, knight2, 3, 4는 hp가 100이고 knight가 가리키는 Pet의 주소를 가지고 있다.

여기서 문제가 4개의 객체가 같은 주소의 pet 객체를 가리킨다는 것이다.

heap 영역
pet객체 0x1111 [ 데이터 ]

stack 영역 
knight[ hp(100) _pet(0x1111) ]
knight2[ hp(100) _pet(0x1111) ]
knight3[ hp(100) _pet(0x1111) ]
knight4[ hp(100) _pet(0x1111) ]

여기서 Knight와 _pet은 생명주기가 같다. 따라서 knight가 소멸될 때 _pet이 가리키는 주소에 있던 pet 객체도 소멸된다. 이후에 knight2, 3, 4가 소멸될 때 똑같은 주소에 있던 pet 객체를 소멸시키는 delete _pet; 이 실행되는데, 이전에 한 번 실행했기 때문에 프로그램이 강제 종료된다.

 

 

깊은 복사(Deep Copy)

얕은 복사와는 달리 멤버 데이터가 주소 값이라면, 데이터를 새로 만들어준다. 즉, 원본 객체가 참조하는 대상까지 새로 만들어서 복사한다. 깊은 복사를 사용하는 방법은 복사 생성자와 복사 대입 연산자를 명시적으로 설정하는 것이다.

class Pet
{
public:
	...
	Pet(const Pet& pet)
	{
		cout << "pet(const Pet&)" << endl;
	}
	...
};

class Knight
{
public:
	...
	// 명시적으로 정의한다.
	Knight(const Knight& knight)
	{
		_hp = knight._hp;
		_pet = new Pet(*(knight._pet));	// 핵심
	}
	Knight& operator=(const Knight& knight)
	{
		_hp = knight._hp;
		_pet = new Pet(*(knight._pet));

		return *this;
	}
	...
}

_pet = new Pet(*(knight._pet)); 은 knight 객체가 가리키는 pet 객체의 주소의 값을 가져온 뒤, 해당 주소에 존재하는 데이터와 같은 pet 객체를 새로 만들어준다는 것이다. 이때 Pet 클래스의 복사 생성자가 실행된다.

heap 영역
pet객체 0x1111 [ 동일 데이터 ]
pet객체 0x2222 [ 동일 데이터 ]
pet객체 0x3333 [ 동일 데이터 ]
pet객체 0x4444 [ 동일 데이터 ]

stack 영역 
knight[ hp(100) _pet(0x1111) ]
knight2[ hp(100) _pet(0x2222) ]
knight3[ hp(100) _pet(0x3333) ]
knight4[ hp(100) _pet(0x4444) ]

 

얕은 복사와 깊은 복사도 면접 단골 질문이다!

 

 

생성 결과

암시적 복사 생성자

  1. 부모 클래스의 복사 생성자 호출
  2. 멤버 클래스(포인터x)의 복사 생성자 호출
  3. 메모리 복사(얕은 복사)

 

명시적 복사 생성자

  1. 부모 클래스의 기본 생성자 호출
  2. 멤버 클래스의 기본 생성자 호출

부모 및 멤버 클래스의 특정 생성자를 호출하고 싶다면 초기화 리스트를 이용하면 된다.

 

 

암시적 복사 대입 연산자

  1. 부모 클래스의 복사 대입 연산자 호출
  2. 멤버 클래스의 복사 대입 연산자 호출
  3. 메모리 복사

 

명시적 복사 대입 연산자

이건 프로그래머가 명시적으로 만들어준대로 작동한다.

 

 

 

 

출처

https://www.inflearn.com/course/%EC%96%B8%EB%A6%AC%EC%96%BC-3d-mmorpg-1/dashboard

 

[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part1: C++ 프로그래밍 입문 - 인프런 | 강의

시리즈를 원활하게 학습하기 위한 기초적인 C++ 문법들에 대해 학습합니다. 어셈블리 언어부터 시작해서 기본 C++ 문법, STL, C++11까지 핵심적인 내용을 압축해서 다루게 됩니다., - 강의 소개 | 인

www.inflearn.com

 

'프로그래머 > CPP_강의정리' 카테고리의 다른 글

cpp 디버깅 기초  (0) 2021.09.28
cpp casting의 4가지 방법  (0) 2021.09.27
cpp 타입 변환  (0) 2021.09.23
cpp 동적 할당과 인자로 받는 클래스 주의점  (0) 2021.09.20
cpp struct와 class의 차이, static  (0) 2021.09.20