[Chap 05] 복사 생성자
복사 생성자
SoSimple sim2 = sim1
또는
SoSimple sim2(sim1)
을 통해서 객체 복사가 일어나는데, 일어나는 과정은 sim2 객체가 생성된 다음에 sim1 에 생성된 멤버 변수를 멤버 대 멤버로 복사가 일어난다.
#include <iostream>
class SoSimple
{
private:
int num1;
int num2;
public:
SoSimple(int n1, int n2) : num1(n1), num2(n2)
{
}
SoSimple(const SoSimple ©) : num1(copy.num1), num2(copy.num2)
{
std::cout << "Called SoSimple(SoSimple ©)" << std::endl;
}
void ShowSimpleData()
{
std::cout << num1 << std::endl;
std::cout << num2 << std::endl;
}
};
int main()
{
SoSimple sim1(15, 30);
std::cout << "생성 및 초기화 직전" << std::endl;
SoSimple sim2 = sim1; // SoSimple sim2(sim1);
std::cout << "생성 및 초기화 직후" << std::endl;
sim2.ShowSimpleData();
return 0;
}
>>>
생성 및 초기화 직전
Called SoSimple(SoSimple ©)
생성 및 초기화 직후
15
30
두 번째 생성자와 같은 경우를 복사 생성자 라고 한다. 복사 생성자의 경우는 다른 일반 생성자와 차이가 있기 때문에 복사 생성자라는 이름이 붙은 것이다.
또한 복사하는 도중에 값이 바뀌면 안되므로 const 를 붙혀서 복사 생성자를 이용한다.
자동으로 삽입이 되는 디폴트 복사 생성자
복사 생성자를 정의하지 않으면, 멤버 대 멤버의 복사를 진행하는 디폴트 복사 생성자가 자동으로 삽입된다.
변환에 의한 초기화! 키워드 explicit으로 막을 수 있다.
디폴트 복사 생성자를 통해서 멤버 변수가 초기화 되는 것을 막기 위해서는 explicit 키워드를 사용할 수 있다.
깊은 복사와 얕은 복사
#include <iostream>
#include <cstring>
class Person
{
private:
char *name;
int age;
public:
Person(char *myname, int myage)
{
int len = strlen(myname) + 1;
name = new char[len];
strcpy(name, myname);
age = myage;
}
void ShowPersonInfo() const
{
std::cout << "이름: " << name << std::endl;
std::cout << "나이: " << age << std::endl;
}
~Person()
{
delete[] name;
std::cout << "called destructor!" << std::endl;
}
};
int main()
{
Person woman1("Sohn soo kyoung", 22);
Person woman2 = woman1;
woman1.ShowPersonInfo();
woman2.ShowPersonInfo();
return 0;
}
>>>
이름: Sohn soo kyoung
나이: 22
이름: Sohn soo kyoung
나이: 22
called destructor!
called destructor!
나는 이렇게 출력결과가 나왔는데, 사실 called destructor 가 한번만 나오는 것이 일반적이라고 한다. 근데 왜 한 번만 나오는 것일까?
위 복사 생성자는 디폴트 복사 생성자를 사용했다. 디폴트 복사 생성자는 객체를 생성하고 그 객체의 멤버 변수를 멤버 대 멤버로 복사하는 구조를 띠게 된다. 그렇게 된다면 하나의 문자열 주소를 두개의 객체가 참조하는 꼴이 되기 때문에 한 번만 called destructor 가 나오는 것이 일반적이라고 한 것이다. 즉, 문자열 주소만 복사가 된 것이기 때문에 결국 하나의 문자열을 참조하는 꼴이 되버린다.
복사 생성자를 사용해서 다른 문자열을 참고하도록 복사하는 것을 깊은 복사 라고 한다.
깊은 복사를 위한 복사 생성자 정의
깊은 복사는 위에서 말했던 것 처럼 문자열까지 복사를 해서 복사된 객체는 다른 주소로 만들어진 문자열을 가리키도록 하는 것이다.
#include <iostream>
#include <cstring>
class Person
{
private:
char *name;
int age;
public:
Person(char *myname, int myage)
{
int len = strlen(myname) + 1;
name = new char[len];
strcpy(name, myname);
age = myage;
}
Person(const Person ©) : age(copy.age)
{
name = new char[strlen(copy.name) + 1];
strcpy(name, copy.name);
}
void ShowPersonInfo() const
{
std::cout << "이름: " << name << std::endl;
std::cout << "나이: " << age << std::endl;
}
~Person()
{
delete[] name;
std::cout << "called destructor!" << std::endl;
}
};
int main()
{
Person woman1("Sohn soo kyoung", 22);
Person woman2 = woman1;
woman1.ShowPersonInfo();
woman2.ShowPersonInfo();
return 0;
}
>>>
이름: Sohn soo kyoung
나이: 22
이름: Sohn soo kyoung
나이: 22
called destructor!
called destructor!
복사 생성자의 호출 시점
- 기존에 생성된 객체를 이용해서 새로운 객체를 초기화하는 경우
- Call-by-Value 방식의 함수 호출 과정에서 객체를 인자로 전달하는 경우
- 객체를 반환하되, 참조형으로 반환하지 않는 경우
메모리 공간의 할당과 초기화가 동시에 일어나는 상황
- int num1 = num2
- 함수가 호출되는 순간 매개변수의 값이 전달되는 경우
- return 을 통해서 함수의 리턴 값을 저장하는 경우
할당 이후, 복사 생성자를 통한 초기화
할당과 초기화가 동시에 이루어지는 경우는 멤버 대 멤버가 복사되도록 하면 된다.
#include <iostream>
class SoSimple
{
private:
int num;
public:
SoSimple(int n) : num(n)
{
}
SoSimple(const SoSimple ©) : num(copy.num)
{
std::cout << "Called SoSimple(const SoSimple ©)" << std::endl;
}
void ShowData()
{
std::cout << "num: " << num << std::endl;
}
};
void SimpleFuncObj(SoSimple ob)
{
ob.ShowData();
}
int main()
{
SoSimple obj(7);
std::cout << "함수 호출 전" << std::endl;
SimpleFuncObj(obj);
std::cout << "함수 호출 후" << std::endl;
return 0;
}
>>>
함수 호출 전
Called SoSimple(const SoSimple ©)
num: 7
함수 호출 후
obj라는 매개변수가 ob 라는 인자로 들어갈 때 할당과 동시에 초기화가 이루어 지는 경우이므로 이는 디폴트 복사 생성자를 사용해서 복사 생성자를 만들 수 있다.
return 이 사용될 때 복사 생성자는 언제 사용이 될까?
#include <iostream>
class SoSimple
{
private:
int num;
public:
SoSimple(int n) : num(n)
{
}
SoSimple(const SoSimple ©) : num(copy.num)
{
std::cout << "Called SoSimple(const SoSimple ©)" << std::endl;
}
SoSimple &AddNum(int n)
{
num += n;
return *this;
}
void ShowData()
{
std::cout << "num: " << num << std::endl;
}
};
SoSimple SimpleFuncObj(SoSimple ob)
{
std::cout << "return 이전" << std::endl;
return ob;
}
int main()
{
SoSimple obj(7);
SimpleFuncObj(obj).AddNum(30).ShowData();
obj.ShowData();
return 0;
}
>>>
Called SoSimple(const SoSimple ©)
return 이전
Called SoSimple(const SoSimple ©)
num: 37
num: 7
obj -> ob(매개변수가 할당 및 초기화) -> Called SoSimple(const SoSimple ©)
-> return 이전
-> return을 통해서 임시 객체 만들어짐 -> Called SoSimple(const SoSimple ©)
-> AddNum을 통해서 임시 객체에 30이 더해짐 -> 임시 객채의 ShowData() -> num: 37
-> obj.ShowData() -> num: 7
반환할 때 만들어진 객체는 언제 사라져요?
- 임시 객체는 다음 행으로 넘어가면 바로 소면되어 버린다.
- 참조자에 참조되는 임시객체는 바로 소멸되지 않는다.
댓글남기기