[C++] 복사 생성자, 깊은 복사와 얕은 복사

sangjun

·

2021. 4. 14. 16:04

반응형

C++의 초기화 방법

C++에서는

int num=20;

int &ref=num;과

 

int num(20);

int &ref(num);으로 두 가지 초기화 방법이 있고 두 가지 결과 또한 동일합니다.

 

하지만 이것은 변수와 변수간의 초기화 방법일 뿐이고

객체와 객체간의 초기화 방법은 약간 다른 개념이 추가되어야 합니다.

왜냐하면 객체에는 수많은 복잡한 내용들이 포함되어 있기 때문에 일반 변수와는 차별성을 두어야 합니다.

 

C++에서의 변수와 객체 초기화 소개에 앞서 아래 코드를 먼저 살펴보겠습니다.

#include<iostream>

class SoSimple {
private:
	int num1;
	int num2;
public:
	SoSimple(int n1, int n2) : num1(n1), num2(n2)
	{}
	void ShowSimpleData()
	{
		std::cout << num1 << std::endl;
		std::cout << num2 << std::endl;
	}
};



int main()
{
	SoSimple sim1(15, 20);
	SoSimple sim2 = sim1;
	sim2.ShowSimpleData();
	return 0;
}

SoSimple생성자에서 멤버변수의 초기화가 이루어지고 있습니다.

즉, sim1객체의 num1과 num2는 각각 15와 20으로 초기화가 이루어진다는 것입니다.

 

하지만 메소드는 sim2.ShowSimpleData()로 sim2의 멤버변수들을 출력하고 있습니다.

출력결과는 15와 20이 출력됩니다. 그 이유를 이제 알아보도록 하겠습니다.

SoSimple sim2=sim1 문장은 디폴트 생성자라는 것에 의해서 sim1과 sim2의 멤버와 멤버 간의 복사가 이루어집니다.

디폴트 생성자란

디폴트 생성자란 SoSimple생성자의 인자인 int형 정수 2개가 아닌 객체가 인자로 넘어왔을 때

컴파일러에 의해서 자동적으로 생성되는 생성자입니다.

 

아래 예제를 통해서 객체를 초기화 할 때 생성자의 인자로 기본 자료형을 넘겨줄 때와 객체를 넘겨줄 떄의 차이점을

확인해보겠습니다.

 

#include <iostream>

using namespace std;

class SoSimple {
private:
	int num1;
	int num2;

public:
	SoSimple(int n1, int n2) :num1(n1), num2(n2) {

	}
	SoSimple(SoSimple& copy):num1(copy.num1),num2(copy.num2)
	{
		cout << "Called SoSimple(soSimple &copy)" << endl;
	}
	void ShowSimpleData()
	{
		cout << num1 << endl;
		cout << num2 << endl;
	}
};


int main()
{
	SoSimple sim1(15, 30);
	cout << "생성 및 초기화 직전" << endl;
	SoSimple sim2 = sim1;
	cout << "생성 및 초기화 직후" << endl;
	sim2.ShowSimpleData();
	return 0;
}

 

출력결과를 살펴본다면 SoSimple sim2=sim1을 호출할 때는 일반 생성자를 호출하지 않고

디폴트 생성자를 호출하는 것을 볼 수 있다.

 

깊은 복사와 얕은 복사

깊은 복사와 얕은 복사의 차이점은 포인터 관점에서 보면 이해가 잘 될 것이다.

위와 같이 디폴트 생성자를 사용할 경우 얕은 복사가 이루어져

두 객체의 멤버변수가 똑같은 메모리 주소를 가르키는 경우

delete키워드를 사용할 때 Double Free Bugs가 발생할 수 있다.

아래는 Double Free Bugs가 발생하는 예제이다.

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;

class Person
{
private:
	char* name;
	int age;
public:
	Person(const char* myname, int myage)
	{
		int len = strlen(name) + 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()
{
	Person man1("Lee dong woo", 29);
	Person man2 = man1;
	man1.ShowPersonInfo();
	man2.ShowPersonInfo();
	return 0;
}

위의 예제를 실행해보면 소멸자가 두 번 실행되어야 하는데 한번만 실행되는 것을 볼 수 있을 것이다.

 

그럼 깊은 복사를 하기 위한 방법을 알아보자.

Person(const Person &copy):age(copy.age)
{
	name=new char[strlen(copy.name)+1];
    strcpy(name, copy.name);
}

방법 : 생성자 오버로딩을 통해 인자를 객체의 형태로 받게 함으로써 똑같은 주소를 가리키지 않게 할 수 있다.

즉, 문자열을 위한 메모리 공간을 따로 할당 받게 된다.

반응형

'프로그래밍' 카테고리의 다른 글

[Network] NAT란  (0) 2021.07.08
[ncurses] linux ui library  (0) 2021.05.16
[JAVA] 접근 지정자  (0) 2021.05.15
[C++] 복사 생성자, 깊은 복사와 얕은 복사  (0) 2021.04.14
[자료구조] 스택과 큐  (0) 2021.04.12
[JAVA] Scanner Class 사용법  (1) 2021.04.10

0개의 댓글