Programming Language/C++

[C++] 깊은 복사와 얕은 복사(Deep Copy, Shallow Copy)

lumana 2024. 3. 12. 22:22

깊은 복사와 얕은 복사(Deep Copy, Shallow Copy)


깊은 복사 / 얕은 복사란

  • 얕은 복사 : 복사된 객체가 원본 객체의 요소들을 참조

    • 예시
    int *a = new int(5);
    int *b = new int(3);
    a = b;
    delete a;
    delete b;
    • 3이 저장된 공간을 두 번 지움

    • 5가 저장된 공간이 안지워짐

    • 위와 같은 복사를 얕은 복사라 한다.(참조만 복사)

    • 객체 내부에 참조 타입이 있는 경우에는 얕은 복사로 인해 문제가 발생할 수 있음
  • 깊은 복사 : 원본 객체의 완전한 복사본을 생성, 복사된 객체는 완전히 독립적인 새로운 객체를 가지게 됨

    • 내부에 있는 모든 객체들까지 재귀적으로 복사함
  • 예시

    int *a = new int(5);
    int *b = new int(3);
    *a = *b; // 깊은 복사
    delete a;
    delete b;
    • b의 값이 복사되어 a의 값으로 저장됨

    • 위와 같은 복사를 깊은 복사라고 한다

    • 내부 객체의 변경이 복사본에 영향을 주지 않음

얕은 복사의 대표적 예시

class Copy {
private:
  int *a;
};
Copy A, B;
// 여기에서
B = A; // 를 하게 되면...
// B.a = A.a 가 되어 얕은 복사가 일어나게 된다.

얕은 복사를 유의하며 String 클래스 구현하기

// string class를 구현하는 코드
#include <iostream>

using namespace std;

class String {
   public:
    String() {
        cout << "String() 생성자 호출" << '\n';
        strData = NULL;
        len = 0;
    }
    String(const char *str) {
        cout << "String(const char *str) 생성자 호출" << '\n';
        // strData = str; --> 얕은 복사
        len = strlen(str);
        strData = new char[len + 1];                          // 널문자가 있기 때문에 len + 1
        cout << "strData 할당: " << (void *)strData << endl;  
        // strData는 char 포인터인지(주소출력), 문자열을 가리키는건지(문자열출력) cout을 보고 컴파일러가 문자열로 인식
        // 따라서 (void*)strData 사용
        /*
        void *ptr = strData;
        *ptr = 'A'; 는 불가능. void*는 주솟값만 가지고 있기때문에, char형인지 int형인지 알 수 없기 때문.
        */
        strcpy(strData, str);  // 깊은 복사
    }
    ~String() {
        cout << "~String() 소멸자 호출" << endl;
        delete[] strData;
        cout << "strData 해제됨: " << (void *)strData << endl;
        strData = NULL;  // delete하고 해제된 메모리에 대한 접근을 막기위한 관습적 방법.
    }

    char *GetStrData() const {
        return strData;
    }

    int GetLen() const {
        return len;
    }

   private:
    char *strData;
    int len;
};

int main(void) {
    String s1("안녕");
    String s2(s1);
    // 기본적으로 소멸은 선언의 역순
    cout << s1.GetStrData() << '\n';
    cout << s2.GetStrData() << '\n';
}
  • 생성자의 매개변수로 받은 문자열에서 strcpy 함수를 이용해 문자열의 길이를 저장

  • 문자열의 길이 + 1 만큼의 크기로 동적할당 후(널문자 때문에 + 1) strcpy 함수를 통해 깊은 복사

  • 위 코드를 실행하면 에러가 발생함

    • 일단, 생성자가 s1을 생성할 때 한번만 호출됨

      • String s2(s1)에서 얕은 복사가 일어남을 알 수 있다.
    • 첫 번째 소멸자 호출까지는 정상적으로 메모리가 해제되다가 두 번째 소멸자 호출과정에서 에러가 발생

      • 이는 얕은 복사가 일어났기 때문에 같은 공간에 대해 메모리를 두 번 해제했기 때문이다.

      • 이를 막기 위해 복사 생성자 오버로딩을 이용함 (다음 챕터에)

참조) 두들낙서 C/CPP 강의

'Programming Language > C++' 카테고리의 다른 글

[C++] 대입 연산자 오버로딩  (0) 2024.03.12
[C++] 복사 생성자 오버로딩  (0) 2024.03.12
[C++] 동적할당  (0) 2024.02.04
[C++] 연산자 오버로딩  (0) 2024.02.04
[C++] 멤버 메서드의 활용  (0) 2024.02.04