얕은 복사와 깊은 복사: C/C++ 메모리 복사의 진짜 의미
C/C++에서 변수 복사는 단순히 '=' 기호 하나로 끝나지 않습니다. 복사의 방식에 따라 프로그램의 동작, 성능, 심지어 심각한 오류까지 좌우될 수 있습니다. 이 글에서는 얕은 복사(shallow copy)와 깊은 복사(deep copy)의 차이를 근본부터 파헤쳐 보겠습니다.
복사란 무엇인가?
복사란 한 객체의 내용을 다른 객체로 복제하는 것입니다. 하지만 '내용'이라는 말이 문제가 됩니다. 객체가 단순한 값(int, double 등)일 때는 문제가 없지만, 객체가 포인터나 동적 메모리를 포함할 때 복사의 정의는 복잡해집니다.
int a = 10;
int b = a; // 단순 복사 (값 복사)
이 경우 a
와 b
는 완전히 독립된 두 개의 변수입니다.
얕은 복사란?
얕은 복사(shallow copy)는 객체의 값을 단순히 비트 단위로 복사하는 것입니다. 포인터가 포함되어 있을 경우 포인터 값 (즉, 메모리 주소)만 복사되며, 실제 데이터는 공유됩니다.
struct MyClass {
int* data;
};
MyClass a;
a.data = new int(42);
MyClass b = a; // 얕은 복사
위 코드에서 b.data
는 a.data
와 같은 메모리 주소를 가리킵니다. 즉, 둘이 동일한 데이터(42)를 공유합니다.
얕은 복사의 위험
- 한 객체에서 메모리를 해제하면 다른 객체가 무효화됨 (Dangling Pointer)
- 동시 수정 시 의도치 않은 결과 발생
- 더블 delete 가능성
깊은 복사란?
깊은 복사(deep copy)는 객체가 소유한 모든 리소스까지 독립적으로 새롭게 복사하는 것입니다. 포인터가 가리키는 데이터까지 새로 복사하여 두 객체가 완전히 독립된 메모리를 가지게 만듭니다.
struct MyClass {
int* data;
// 깊은 복사 생성자
MyClass(const MyClass& other) {
data = new int(*(other.data));
}
// 소멸자
~MyClass() { delete data; }
};
이제 a
와 b
는 동일한 값을 가지고 있지만, 서로 다른 메모리 공간에 존재합니다. 한 쪽이 소멸되어도 다른 쪽은 영향을 받지 않습니다.
얕은 복사 vs 깊은 복사 비교
특징 | 얕은 복사 | 깊은 복사 |
---|---|---|
포인터 멤버 | 주소 복사 | 실제 데이터 복사 |
메모리 독립성 | 공유 | 완전 독립 |
자원 해제 | 다중 해제 위험 | 안전 |
비용 | 저렴 | 비쌈 |
C++에서 복사의 기본 동작: 컴파일러가 만들어주는 얕은 복사
C++에서는 특별히 복사 생성자, 대입 연산자를 작성하지 않으면 컴파일러가 기본 복사 생성자와 기본 대입 연산자를 만들어주는데, 이들은 기본적으로 **얕은 복사**를 수행합니다.
MyClass a;
MyClass b = a; // 컴파일러가 얕은 복사 생성자 자동 생성
따라서 리소스를 소유하는 객체를 만들 때는 반드시 복사 생성자, 대입 연산자, 소멸자를 명시적으로 정의해야 합니다. (Rule of Three)
Rule of Three / Rule of Five
리소스를 소유하는 클래스라면 반드시 다음 세 가지를 정의해야 안전합니다:
- 복사 생성자
- 대입 연산자
- 소멸자
이를 Rule of Three라 부릅니다.
C++11 이후 이동 생성자, 이동 대입 연산자까지 포함하면 Rule of Five로 확장됩니다.
스마트 포인터와 깊은 복사의 부담 완화
스마트 포인터(std::unique_ptr
, std::shared_ptr
)를 사용하면 깊은 복사의 구현 부담이 크게 줄어듭니다. 스마트 포인터는 소유권과 복사 정책을 내부에서 잘 관리해주므로 많은 경우 직접 복사 생성자를 구현할 필요가 사라집니다.
복사 전략 선택 기준
- 리소스를 소유하지 않는 단순 객체 → 기본 얕은 복사 충분
- 동적 메모리 직접 소유 → 깊은 복사 필요
- 스마트 포인터 사용 → 복사 부담 최소화
- 대규모 리소스 → 복사보다는 Move Semantics 적극 활용
감사합니다.
'프로그래밍 > C\C++' 카테고리의 다른 글
[Effective C++ 정리 #13] RAII란 무엇인가? RAII로 자원 관리 방법 (2) | 2025.06.23 |
---|---|
[Effective C++ 정리 #12] 복사 생성자와 복사 대입 연산자 직접 작성 시 유의할 점 (0) | 2025.06.22 |
[Effective C++ 정리 #11] operator= 시 자기 자신 대입 유의! (0) | 2025.06.21 |
[Effective C++ 정리 #10] 대입 연산자의 return *this의 의미?! (4) | 2025.06.20 |
C/C++ 참조자(reference) 내용 정리 (4) | 2025.06.19 |