티스토리 뷰
깊은 복사와 얕은 복사
모든 프로세스에서 복사는 수없이 일어난다.(항상 그렇다는건 아니지만...) 예전 단원에서는 복사와 이동의 차이를 언급한 적이 있다.
복사는 실제 값의 복사본을 생성하여 다른 변수에 할당하고 이동은 실제 값을 다른 변수에 이동시키고 원본 변수는 제거되거나 변형되는 것이다.
복사는 특정 변수가 가진 실제값을 다른 변수에게도 전달해주는 것을 의미한다. C++에서 복사의 의미는 메모리 관리와 객체 간의 상호 작용을 다루는 데 중요하고 효율적인 복사는 메모리 관리와 프로그램의 성능에 영향을 미칠 수 있다. 이에 우리는 깊은 복사와 얕은 복사라는 개념을 공부해보려 한다.
얕은 복사 (Shallow copy)
얕은 복사는 쉽게 말하자면 두 개 이상의 포인터 변수가 동일한 값의 주소를 가진것을 의미한다.
#include <iostream>
using namespace std;
int main()
{
int *a = new int(3);
int *b = a;
*a = 5;
cout << *a << endl;
cout << *b << endl;
delete a;
return 0;
}
위 코드를 보며 어떤 결과가 예측되는가?
정답은
왜 그럴까? 바로 두 개의 변수 모두 힙영역에 할당된 동일한 주소값을 보고있기 때문이다.
좀더 확실히 알아보기 위해서 두 포인터 변수와 실제 값의 주소를 출력해보겠다.
#include <iostream>
using namespace std;
int main()
{
int *a = new int(3);
int *b = a;
cout << "a의 주소: " << &a << ", a가 가리키는 값의 주소: " << a << ", a가 가리키는 값: " << *a << endl;
cout << "b의 주소: " << &b << ", b가 가리키는 값의 주소: " << b << ", b가 가리키는 값: " << *b << endl;
*a = 5;
cout << "변경 후 - a의 주소: " << &a << ", a가 가리키는 값의 주소: " << a << ", a가 가리키는 값: " << *a << endl;
cout << "변경 후 - b의 주소: " << &b << ", b가 가리키는 값의 주소: " << b << ", b가 가리키는 값: " << *b << endl;
delete a;
return 0;
}
a의 주소: 0x7ffc24ea1798, a가 가리키는 값의 주소: 0x5600d60a4eb0, a가 가리키는 값: 3
b의 주소: 0x7ffc24ea17a0, b가 가리키는 값의 주소: 0x5600d60a4eb0, b가 가리키는 값: 3
변경 후 - a의 주소: 0x7ffc24ea1798, a가 가리키는 값의 주소: 0x5600d60a4eb0, a가 가리키는 값: 5
변경 후 - b의 주소: 0x7ffc24ea17a0, b가 가리키는 값의 주소: 0x5600d60a4eb0, b가 가리키는 값: 5
이를 통해 두 포인터 변수는 완벽히 다른 변수이지만 두 포인터가 가리키는 주소는 동일하여 같은 값을 변경할 수 있는 권한이 발생하는 것을 알수있다.
깊은 복사 (Deep copy)
이번에는 얕은 복사와 반대인 깊은 복사에 대해서 알아보겠다. 깊은 복사란 실제 값의 주소값을 가져오는 것이 아닌 완벽히 실제값을 복제하여 다른 값에 복사하는 것을 말한다.
#include <iostream>
using namespace std;
int main()
{
int *a = new int(3);
int *b = new int(*a);
*a = 5;
cout << *a << endl;
cout << *b << endl;
delete a;
delete b;
return 0;
}
위 코드는 깊은 복사의 예시이다. 이도 마찬가지로 결과를 예측해보자.
정답
이러한 결과가 나오는 이유는 두 변수가 완전히 다른 메모리 영역의 주소 값을 가지기 때문이다.
이제 위 예제를 증명할 코드를 작성해보자.
#include <iostream>
using namespace std;
int main()
{
int *a = new int(3);
int *b = new int(*a);
cout << "a의 주소: " << &a << ", a가 가리키는 값의 주소: " << a << ", a가 가리키는 값: " << *a << endl;
cout << "b의 주소: " << &b << ", b가 가리키는 값의 주소: " << b << ", b가 가리키는 값: " << *b << endl;
*a = 5;
cout << "변경 후 - a의 주소: " << &a << ", a가 가리키는 값의 주소: " << a << ", a가 가리키는 값: " << *a << endl;
cout << "변경 후 - b의 주소: " << &b << ", b가 가리키는 값의 주소: " << b << ", b가 가리키는 값: " << *b << endl;
delete a;
delete b;
return 0;
}
a의 주소: 0x7ffcf4958ba8, a가 가리키는 값의 주소: 0x55fb8dfa9eb0, a가 가리키는 값: 3
b의 주소: 0x7ffcf4958bb0, b가 가리키는 값의 주소: 0x55fb8dfa9ed0, b가 가리키는 값: 3
변경 후 - a의 주소: 0x7ffcf4958ba8, a가 가리키는 값의 주소: 0x55fb8dfa9eb0, a가 가리키는 값: 5
변경 후 - b의 주소: 0x7ffcf4958bb0, b가 가리키는 값의 주소: 0x55fb8dfa9ed0, b가 가리키는 값: 3
위 코드를 보면 a와 b 포인터 변수와 각 포인터가 가리키고 있는 실제값의 주소도 완벽히 다른 것을 볼 수 있다. 유일하게 같은 점은 처음 할당 받은 실제 값이 전부이다.
즉, 깊은 복사는 서로가 아무런 영향도 미치지 못하는 것이다.
객체 복사
깊은 복사와 얕은 복사의 개념은 굉장히 간단하고 쉽게 이해할 수 있다.
다만, 실제 깊은 복사와 얕은 복사가 사용되는 곳은 변수 뿐만이 아닌 클래스, 구조체 등 상황에 따라 필요에 따라 다르게 적용해야할 수 있다.
이번에는 클래스 객체를 복사하는 것을 해보자.
#include <iostream>
using namespace std;
class A {
public:
A(int x, int *y) : x(x), y(y) {}
int x;
int *y;
};
int main() {
A a(1, new int(2));
A b = a;
a.x = 3;
*a.y = 4;
cout << b.x << endl;
cout << *b.y << endl;
delete a.y;
}
1
4
위 코드의 결과는 1과 4가 나왔다. 어째서일까?
바로 b 객체에 a 객체를 복사 연산생성자를 통해 적용하는데 기본 복사 생성자는 얕은 복사를 하기 때문이다.
이과정에서 일반 변수인 x는 주소값을 가질 수 없는 형태이기에 자동적으로 깊은 복사가 일어났지만, 포인터 변수인 y는 주소값을 받는 얕은 복사가 일어나는 것이다.
'Computer Language > C & C++' 카테고리의 다른 글
[C++] 상수형 메서드 (0) | 2023.12.18 |
---|---|
[C++] 정적 멤버 (1) | 2023.12.18 |
[C++] 동적 할당 (1) | 2023.12.14 |
[C++] 7) C++ 스타일 기본 문법 :: 생성자와 소멸자 (0) | 2023.11.27 |
[C++] 메모리 패딩 & 메모리 얼라이어먼트 (Memory Padding & Alignment) (1) | 2023.11.27 |