[C++] Chapter 05 - 복사 생성자

Lee Jeong Min·2021년 1월 2일
0

Cpp

목록 보기
5/16
post-thumbnail

05-1 '복사 생성자'와의 첫 만남

C++ 스타일의 초기화

C++에서 변수와 참조자를 초기화 하는 방법은 다음과 같다.

int num = 20;
int &ref = num;

int num(20);
int &ref(num);

이를 객체의 생성으로 옮기면

SoSimple sim2 = sim1;
SoSimple sim2(sim1);

다음과 같고 저러한 형태를 클래스에서 받으려면

SoSimple(SoSimple & copy)
{
}

와 같은 생성자가 있어야함. 그러나 클래스 내부에 저러한 생성자가 없더라도 자동으로 복사 생성자를 삽입해줌

디폴트 복사 생성자(자동 삽입)

복사생성자를 정의하지 않는 경우에 멤버 대 멤버의 복사를 진행하는 디폴트 복사 생성자가 자동으로 삽입됨.

또한

SoSimple sim2 = sim1; 

이 자동으로

SoSimple sim2(sim1);

다음과 같이 변화가 되는데 이를 묵시적 변환이라고 하며 이를 위해 다음과 같이 생성자에 explicit을 붙여주면 더 이상 묵시적 변환이 발생하지 않음

explicit SoSimple(const SoSimple &copy) : num(copy.num1), num2(copy.num2)
{
}

참고로 복사생성자에서 참조형 선언을 의미하는 &를 삽입하지 않으면 복사 생성자의 호출은 무한루프에 빠져버리게 됨.


05-2 '깊은 복사'와 '얕은 복사'

디폴트 복사 생성자의 문제점

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

class Person
{
private:
	char* name;
	int age;
public:
	Person(const 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;
}

이러한 코드에서 발생하는 문제점은 Person man2 = man1이 얕은 복사여서 단순히 복사만 하기 때문에 delete가 man1과 man2에 적용될 때(man2 먼저 삭제한다고 가정) man2는 따로 자신이 공간을 가진 것이 아닌 man1을 참조하고 있는데 man1의 공간을 먼저 지우게 되어 man1에 delete가 적용될 때 오류가 발생하게 된다.

깊은복사 코드

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

class Person
{
private:
	char* name;
	int age;
public:
	Person(const char* myname, int myage)
	{
		int len = strlen(myname) + 1;
		name = new char[len];
		strcpy(name, myname);
		age = myage;
	}
	Person(const Person& person)
	{
		name = new char[strlen(person.name) + 1];
		strcpy(name, person.name);
		age = person.age;
	}
	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;
}

05-3 복사 생성자의 호출시점

복사 생성자의 호출 시점은 총 3가지

  • 기존에 생성된 객체를 이용해서 새로운 객체를 생성하는 경우
  • Call-by-value 방식의 함수호출 과정에서 객체를 인자로 전달하는 경우
  • 객체를 반환하되, 참조형으로 반환하지 않는 경우

Case1

int num1 = num2;

Case2

int SimpleFunc(int n)
{
	....
}
int main(void)
{
    int num = 10;
    SimpleFunc(num); // 호출되는 순간 매개변수 n이 할당과 동시에 초기화
    ...
}

Case3

int SimpleFunc(int n)
{
	...
    return n; // 반환하는 순간 메모리 공간이 할당되면서 동시에 초기화!
}
int main(void)
{
    int num = 10;
    cout << SimpleFunc(num) << endl;
    ....
}

case3의 결과로 return할 시에 임시 변수 및 객체가 생성됨.

임시객체

#include <iostream>
using namespace std;

class Temporary
{
private:
	int num;
public:
	Temporary(int n) : num(n)
	{
		cout << "create obj: " << endl;
	}
	~Temporary()
	{
		cout << "destroy obj:" << endl;
	}
	void ShowTempInfo()
	{
		cout << "My num is " << num << endl;
	}
};

int main(void)
{
	Temporary(100);
	cout << "********* after make!" << endl << endl;

	Temporary(200).ShowTempInfo();
	cout << "********* after make!" << endl << endl;

	const Temporary& ref = Temporary(300);
	cout << "********* end of main!" << endl << endl;
	return 0;
}

실행결과

결과를 설명하기에 앞서 객체의 멤버함수를 호출하기 위해 필요한 3가지는 다음과 같다

  • 객체에 붙여진 이름
  • 객체의 참조 값
  • 객체의 주소 값

위의 Temporary(200).ShowTempInfo(); 도 (임시객체의 참조 값).ShowTempInfo()와 같은 문장 구성

위로 알 수 있는 결과는 임시객체는 다음 행으로 넘어가면 바로 소멸되어 버리지만 참조자에 참조되는 임시 객체는 바로 소멸되지 않는다.

참조되는 임시객체의 소멸 시기

#include <iostream>
using namespace std;

class SoSimple
{
private:
	int num;
public:
	SoSimple(int n) : num(n)
	{
		cout << "New Object: " << this << endl;
	}
	SoSimple(const SoSimple& copy) : num(copy.num)
	{
		cout << "New Copy obj: " << this << endl;
	}
	~SoSimple()
	{
		cout << "Destroy obj:" << this << endl;
	}
};

SoSimple SimpleFuncObj(SoSimple ob)
{
	cout << "Parm ADR: " << &ob << endl;
	return ob;
}

int main(void)
{
	SoSimple obj(7);
	SimpleFuncObj(obj);

	cout << endl;
	SoSimple tempRef = SimpleFuncObj(obj);
	cout << "Return Obj" << &tempRef << endl;
	return 0;
}

실행결과

이를 통해 처음 실행된 SoSimple obj(7); 객체는 return 0, 즉 함수가 종료되기 전까지 생성해 있다가 사라지며, SoSimple tempRef = SimpleFuncObj(obj); 로 인해 tempRef에서 참조하는 객체의 소멸은 tempRef가 함수가 종료되는 시점이다.
또한 여기서 알아두어야 할 점은 tempRef가 추가로 객체를 생성하지 않고 임시객체에 이름만 할당하고 있음을 보여준다는 것이다(객체의 생성 수를 하나 줄여서 효율성을 높이기 위해) --> tempRef와 임시객체의 주소 값이 동일하다는 것에서 알 수 있음

profile
It is possible for ordinary people to choose to be extraordinary.

0개의 댓글