복사 생성자는 어떤 객체를 새로 생성되는 객체에 복사하는 데 사용됩니다.
복사 생성자는 일반적인 대입에 사용되는 것이 아니라 값 전달에 의한 함수 매개변수 전달을 포함한 초기화 작업에 사용됩니다.
복사 생성자의 원형
ClassName(const ClassName &);
복사 생성자의 호출 시기
복사 생성자는 새로운 객체가 생성되어 같은 종류의 기존 객체로 초기화될 때마다 호출됩니다.
복사 생성자의 호출 시기는 아래의 코드와 같습니다.
Person Carl();
Person Plorence(Carl); //복사 생성자 호출
Person Plorence =Carl; //복사 생성자 호출
Person Plorence = Person(Carl); //복사 생성자 호출
Person * Plorence = new Person(Carl); //복사 생성자 호출
위 네 가지의 정의 선언이 복사 생성자를 호출합니다.
복사 생성자가 하는 일
디폴트 복사 생성자는 static 멤버를 제외한 멤버들을 멤버별로 복사합니다. 이것을 얕은 복사라고 부릅니다.
복사 생성자로 멤버들을 멤버별로 복사했을 때를 깊은 복사라고 부릅니다.
class Person {
private:
int age;
int kg;
public:
Person() {
age = 0;
kg = 0;
}
Person(int p_age, int p_kg) {
age = p_age;
kg = p_kg;
}
};
int main(void) {
Person per = Person(15,50);
Person per1 = per;
}
위 코드에서 복사 생성자를 호출하고 있습니다.
복사 생성자를 호출하면 내부적으로 멤버별 대응 복사를 합니다.
간단히 말하면 오른쪽 피연산자의 각각 멤버의 값이 왼쪽 피연산자 멤버에 복사됩니다.
그 결과 per1 객체의 멤버 age는 15, kg는 50이라는 값을 가지게 됩니다.
하지만 위의 예제 코드에서는 아무 문제가 없습니다.
그런데 동적 메모리나 문자열 같은 '값'을 복사하면 안 되는 것들은 문제가 생기게 됩니다.
(문자열에서 =는 문자열 복사가 아니고 주소를 넣는 겁니다. 그래서 문제가 발생하게 됩니다.)
문제가 발생하는 상황
디폴트 복사 생성자의 호출이 끝난 시점부터 문제가 발생합니다.
#include <iostream>
class Person {
private:
char * name;
public:
void Show() const {
std::cout << name;
}
~Person() {
delete name;
}
};
int main(void) {
Person * per = new Person("Plorence");
per->Show();
Person * per1 = per;
delete per;
per1->Show(); //문제 발생
return 0;
}
복사 생성자를 정의하지 않았으니 얕은 복사가 됩니다.
그러면 per 객체의 멤버 변수 name의 주소 값이 per1 객체의 멤버 변수 name에 복사됩니다.
즉 둘은 같은 주소 값을 지시하고 있습니다.
한쪽에서 소멸시킨다면, 남은 한쪽에서 소멸된 메모리 영역을 접근하는 치명적인 문제가 발생하게 됩니다.
여기서 또 다른 문제가 있는데 같은 주소값을 지시하니 한쪽에서 값을 바꾸면 다른 한쪽도 영향이 있습니다.
21번째 줄 코드를 실행 후 22번째 줄 코드를 실행하기 전 상태입니다.
아래를 보면 per 객체와 per1의 객체의 멤버 변수가 서로 같은 메모리 공간을 가리킨다는 걸 알 수 있습니다.
위 이미지처럼 컴파일러가 생성한 복사 생성자는 순수 '값'복사 이므로 문제가 되지 않기 위해 새로운 복사 생성자를 정의해줘야 합니다.
해결 방법
#include <iostream>
#include <string.h>
class Person {
private:
char * name;
public:
Person(const char * str) {
name = new char[strlen(str) + 1];
strcpy_s(name, strlen(str)+1, str);
}
Person(const Person & per) { //복사 생성자 정의
name = new char[strlen(per.name) + 1];
strcpy_s(name, strlen(per.name) + 1, per.name);
}
void Show() const {
std::cout << name;
}
~Person() {
delete name;
}
};
int main(void) {
Person * per = new Person("Plorence");
per->Show();
Person * per1 = new Person(*per);
delete per;
per1->Show();
return 0;
}
앞에서의 예제와는 다르게 새로운 공간을 만들고 그 공간에 문자열을 복사하였습니다.
그래서 per 객체의 멤버 변수 name을 할당 해제해도 문제가 없습니다
댓글