C++

04. C++_얕은복사/깊은복사

만수르코딩방 2024. 3. 24. 23:54

객체를 복사한다는 것은 내용을 다른 객체로 복사하는 것을 의미하는데, 클래스의 객체가 다른 객체로 복사될 때는 보통 매개변수로 같은 클래스의 객체를 받아서 그 객체의 내용을 복사하여 새로운 객체를 생성하는 복사 생성자 (copy Constructor)에 의해 복사가 수행됩니다. 이 때, 복사 생성자가 어떻게 구현이 되었냐에 따라 객체 복사가 깊은 복사인지, 얕은 복사인지 결정되게 됩니다. 

이번 포스팅에서는 깊은 복사, 얕은 복사에 대한 개념과 간단한 예제에 대해 알아보겠습니다.

깊은 복사(Deep copy)

깊은 복사는 객체를 복사할 때 해당 객체가 동적으로 할당된 메모리를 사용하고 있다면, 이 동적 메모리까지 새롭게 할당하고 복사하여 두 객체가 완전히 독립적인 데이터를 갖도록 하는 것입니다. 따라서 포인터 변수와 같이 동적 할당된 메모리를 사용하는 변수는 변수가 가리키는 주소값을 복사하는 것이 아닌 메모리 자체를 복사해주어 복사된 두 객체가 독립적인 메모리를 갖도록 합니다. 

 

- 문자열을 가리키는 포인터 변수에 대한 깊은 복사

#include <iostream>
#include <cstring>
using namespace std;

class Cat{
  char* name;
  int age;
public:
  Cat(const char* n, int a) : age(a){
    name = new char[strlen(n)+1];
    strcpy(name,n);
  }
  Cat(const Cat& c) //깊은 복사를 하기 위해 복사 생성자를 만들자
    :age(c.age) //포인터가 아닌 멤버는 그냥 복사
{
  name = new char[strlen(c.name)+1]; //포인터는 주소가 아닌 메모리 자체를 복사
  strcpy(name, c.name);
}
  ~Cat(){
    delete[] name;
  }
  void print(){
    cout<<name<<endl;
    cout<<age<<endl;
  }
};

int main(){
  Cat c1("NABI", 2);
  Cat c2 = c1; //c2(c1)으로도 복사 생성자 호출 가능
  c2.print();
}

- 정수를 가리키는 포인터 변수에 대한 깊은 복사 

#include <iostream>

class MyClass {
private:
	int* p;
	int sz;
public:
	MyClass(int size, int value) {
		sz = size;
		p = new int[sz];
		for (int i = 0; i < sz; i++) {
			p[i] = value;
		}
	}
	MyClass(const MyClass& obj) { //깊은 복사를 하기 위해 복사생성자를 만들자
		sz = obj.sz; //포인터가 아닌 멤버는 그냥 복사
		p = new int[sz]; //포인터는 주소가 아닌 메모리 자체를 복사
		memcpy(p, obj.p, sizeof(int)*sz); 
	}
	~MyClass() { if (p != nullptr) delete[] p; }
	void print() {
		for (int i = 0; i < sz; i++) {
			std::cout << p[i] << " ";
		}
		std::cout << std::endl;
	}
};

int main() {

	MyClass mc(3, 2);
	MyClass mc2(mc); //mc2 = mc로도 복사생성자 호출 가능

	mc.print();
	mc2.print();

	return 0;
}

 

얕은 복사(Shallow copy)

얕은 복사는 객체 멤버변수들을 다른 객체로 단순히 복사하는 것으로, 객체 내에 포인터 등이 있을 경우 포인터가 가리키는 메모리 주소가 복사되기 때문에 두 객체가 같은 데이터를 참조하게 됩니다. 이 경우, 하나의 객체에서 해당 메모리를 수정하면 다른 객체에서도 영향을 미치며, 하나의 객체가 메모리를 해제하면, 다른 객체는 더이상 유효한 데이터를 가지고 있지 않게 됩니다. 이는 예상치 못한 동작이나 버그를 유발할 수 있으며, 특히 동적 할당된 메모리를 사용하는 객체를 복사할 때 유의하여야 합니다.

#include <iostream>

class MyArray {
public:
    int size;
    int *data;

    // 얕은 복사를 수행하는 복사 생성자
    MyArray(const MyArray& other) : size(other.size), data(other.data) {}

    // 생성자
    MyArray(int s) : size(s), data(new int[s]) {
        for (int i = 0; i < size; ++i) {
            data[i] = 0;
        }
    }

    // 소멸자
    ~MyArray() {
        // 동적 할당된 메모리 해제
        delete[] data;
    }
};

int main() {
    MyArray arr1(3);
    arr1.data[0] = 1;
    arr1.data[1] = 2;
    arr1.data[2] = 3;

    MyArray arr2 = arr1;  // 얕은 복사 생성자 호출

    std::cout << arr1.data[0] << std::endl;  // 출력 결과: 1
    std::cout << arr2.data[0] << std::endl;  // 출력 결과: 1

    // arr1의 소멸자가 호출되면 arr1이 가리키는 동적 메모리가 해제됨
    // arr2는 여전히 같은 메모리를 가리킴

    // 다음 줄에서 arr2의 소멸자가 호출되면 이미 해제된 메모리에 접근하려고 하게 됨
    // 이는 미정의 동작으로 이어질 수 있음

    return 0;
}

'C++' 카테고리의 다른 글

07. C++_call by value/call by pointer/ call by reference 이해하기  (0) 2024.05.16
05. C++_파일 입출력  (0) 2024.05.15
03. C++_invoke  (0) 2024.03.24
02. C++_Initializer_list  (1) 2024.03.18
01. C++_Placement new  (0) 2024.03.11