🌟 복사 생성자
더보기
!! 복사 생성자를 왜 사용할까?
- C++에서 복사 생성자를 사용하는 핵심 이유는 메모리 문제를 방지하기 위해서다.
- 특히 동적 메모리나 포인터를 다루는 클래스에서는 복사 생성자가 필수.
디폴트 복사 생성자 VS 사용자 정의 복사 생성자
디폴트 복사 생성자 (컴파일러 제공)
- 컴파일러가 자동으로 생성
- 얕은 복사(Shallow Copy) 수행
- 모든 멤버를 단순히 bit-wise 복사
사용자 정의 복사 생성자
- 사용자가 직접 작성
- 보통 깊은 복사(Deep Copy)를 위해 만듦
멤버 대 멤버 복사의 의미
- 디폴트 생성자의 "멤버 대 멤버 복사"는 포인터 주소 자체를 복사한다는 뜻.
class Test {
int* ptr;
int value;
};
Test t1;
t1.ptr = new int(100); // ptr이 메모리 주소 0x1000을 가리킨다고 가정
t1.value = 50;
Test t2 = t1; // 디폴트 복사 생성자 호출
디폴트 복사 생성자가 하는 일:
t2.ptr = t1.ptr; // 주소값 복사 (0x1000)
t2.value = t1.value; // 값 복사 (50)
// 결과: t1.ptr과 t2.ptr 둘 다 같은 메모리(0x1000)를 가리킴 → 얕은 복사
문제가 되는 상황
class Student {
char* name;
public:
Student(const char* n) {
name = new char[strlen(n)+1];
strcpy(name, n);
}
~Student() { delete[] name; }
};
Student s1("홍길동");
Student s2 = s1; // 복사 생성자 호출
복사 생성자가 없으면:
- s1과 s2가 같은 메모리 주소를 가르킴
- 프로그램 종료시 같은 메모리를 두 번 삭제하려고 해서 크래시 발생
해결책: 깊은 복사
class Student {
char* name;
int age;
public:
// 사용자 정의 복사 생성자 (깊은 복사)
Student(const Student& other) {
name = new char[strlen(other.name)+1]; // 새 메모리 할당
strcpy(name, other.name); // 내용 복사
age = other.age; // 값 복사
}
~Student() {
delete[] name;
}
};
복사 생성자가 호출되는 경우
Student s1("홍길동");
// 모두 복사 생성자 호출
Student s2 = s1; // 묵시적 변환
Student s3(s1); // 직접 호출
Student s4{s1}; // 중괄호 초기화
언제 복사 생성자를 직접 정의해야 할까?
만들어야 하는 경우
- 포인터 멤버가 있을 때
- 동적 메모리를 할당할 때
- 리소스를 관리할 때 (파일, 네트워크 등)
만들 필요 없는 경우
- 기본 데이터 타입만 있을 때 (int, double, bool 등)
간단한 판단 기준
"소멸자에서 뭔가를 해제해야 하나?"
- Yes → 복사 생성자 필요
- No → 디폴트로 충분
용어 정리
객체 대 객체 복사 vs 멤버 대 멤버 복사 (이 둘은 같은 일을 다른 관점으로 본 것. 문법도 동일)
- 객체 대 객체 복사: 전체 객체를 통으로 복사하는 개념
- 멤버 대 멤버 복사: 객체 안의 각 멤버 변수를 하나씩 복사하는 방식
디폴트 복사 생성자: 깊은 복사 vs 얕은 복사 논란
- 디폴트 생성자가 "깊은 복사"인지 "앝은 복사"인지에 대해서는 상황에 따라 관점이 다름
학술적 관점: "디폴트도 깊은 복사"
!! 포인터가 없는 일반적인 경우:
class Simple {
int a;
double b;
char c;
};
Simple s1;
Simple s2 = s1; // 모든 멤버가 완전히 독립적으로 복사됨
이 경우 모든 멤버가 완전히 독립적으로 복사되므로 "깊은 복사"라고 볼 수 있음.
실무/일반적 관점: "디폴트는 얕은 복사"
포인터가 있는 문제 상황을 기준:
class Student {
char* name; // 포인터 멤버
};
Student s1("홍길동");
Student s2 = s1; // 주소만 복사됨 → 문제 발생
이 경우 포인터 주소만 복사되어 메모리 문제가 발생하므로 "얕은 복사"라 함
디폴트 복사 생성자는 항상 "멤버별 단순 복사"를 수행함.
- 포인터가 없으면: 결과적으로 깊은 복사와 같은 효과
- 포인터가 있으면: 주소만 복사되어 얕은 복사 문제 발생
혼동을 피하기 위해 다음과 같이 표현하는 것이 좋다.
- "디폴트 복사 생성자는 멤버별 단순 복사를 수행"
- "포인터 멤버가 있으면 얕은 복사 문제가 발생할 수 있다."
핵심 정리
- 포인터나 동적 메모리가 있는 클래스에서는 복사 생성자를 직접 만들어야 한다.
- 디폴트 복사 생성자는 멤버별 단순 복사를 수행하며, 포인터가 있으면 메모리 문제를 일으킬 수 있음
- 문법은 항상 동일하며, 구현에 따라 얕은/깊은 복사가 결정됨
- 컴파일러가 자동 제공하는 이유는 대부분의 경우 단순 복사면 충분하기 때문
- 깊은/얕은 복사 용어는 상황과 관점에 따라 다르게 해석될 수 있으므로 정확한 맥락 이해하는 것이 중요