ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [C++] 얕은 복사(shallow copy) vs 깊은 복사(deep copy)
    Language/C, C++ 2019. 7. 4. 22:57

    복사 생성

    객체를 생성할 때, 기존에 있던 객체를 복사해서 생성하는 방법을 복사 생성이라고 합니다.

    class Person {
    	int age;
    public:
    	Person(int age) { this->age = age; }
    };
    
    int main() {
    	Person p1(20); // 원본 객체. age = 20으로 생성
    	Person p2(p1); // 복사된 객체. 원본 객체 p1을 복사해 생성
    }

    기본적으로 복사된 객체는 원본 객체의 멤버 값과 동일한 멤버 값을 가집니다.

    위의 코드에서, p2는 p1을 복사했으므로 멤버 변수 age의 값이 20으로 복사됩니다.

     

    디폴트 복사 생성자

    그런데, Person 클래스의 생성자는 Person(int age) 뿐인데 왜 Person p2(p1); 라인에서 컴파일 오류가 발생하지 않을까요?

     

    이는 컴파일러가 묵시적으로 디폴트 복사 생성자를 삽입하고 실행되도록 컴파일하기 때문입니다.

     

    직접 복사 생성자를 작성하지 않았다면 컴파일러는 다음과 같은 코드를 삽입하여 컴파일합니다.

    Person(Person &p) {
    	this->age = p.age;
    }

    위는 얕은 복사(shallow copy)의 예시입니다.

    컴파일러가 임의로 삽입하는 디폴트 복사 생성자는 기본적으로 얕은 복사를 수행하도록 구성됩니다.

     

    얕은 복사의 문제점

    얕은 복사는 문제점을 가지고 있습니다.

    만약에 클래스의 멤버 중에 포인터 변수가 있다면 어떻게 될까요?

    class Person {
    	int age;
    	char *name;
    public:
    	Person(int age, char *name) { // 생성자
    		this->age = age;
    		this->name = new char [10]; // 동적 메모리 할당
    		strcpy(this->name, name);
    	}
    	~Person() { // 소멸자
    		delete[] name; // 동적 할당된 메모리 해제
    	}
    };
    
    int main() {
    	Person p1(20, "Mike");
    	Person p2(p1);
    }

    Person 클래스의 멤버에 포인터 변수 name을 추가하고 문자열 공간을 동적으로 할당했습니다.

     

    복사 생성자를 작성하지 않았으므로, 컴파일러는 다음과 같은 디폴트 복사 생성자를 삽입할 것입니다.

    Person(Person& p) {
    	this->age = p.age;
    	this->name = p.name;  // 메모리 주소값(포인터) 그대로 복사
    }

    얕은 복사로 모든 멤버를 복사하게 되면, p1.name의 메모리 주소가 p2.name에 그대로 복사되면서 둘은 같은 메모리 공간을 공유합니다.

    문제는 소멸자가 실행될 때 발생합니다.

     

    생성자의 실행 순서와 반대로 소멸자가 실행되므로, p2.name이 가리키는 주소에 할당된 메모리가 먼저 해제됩니다.

     

    다음으로 p1.name이 가리키는 주소에 할당된 메모리를 해제하려 하지만 이미 해제된 상태이므로 메모리 접근 에러가 발생합니다.

    깊은 복사(deep copy)

    깊은 복사(deep copy)는 단순 복사를 넘어 동적으로 메모리를 할당해야 하는 포인터 변수까지 고려해 복사하는 방법으로, 얕은 복사의 문제점을 해결합니다.

    Person(Person& p) { // 깊은 복사 생성자 명시적 작성
    	this->age = age;
    	this->name = new char [10]; // 새로운 메모리를 동적으로 할당하고
    	strcpy(this->name, p.name); // p.name의 값을 복사
    }

    다음처럼 객체가 복사될 때 새로운 메모리 주소를 할당하여 값만 복사하는 깊은 복사 생성자를 작성해줌으로써 얕은 복사의 문제점을 해결할 수 있습니다.

     

    멤버 변수에 포인터가 없다면 복사 생성자를 따로 작성하지 않아도 크게 문제 될 것은 없어 보입니다.

    하지만 혹시 모를 상황에 대비해 명시적으로 복사 생성자를 작성하는 것이 사전에 오류를 방지할 수 있는 바람직한 프로그래밍 습관이 아닐까 생각합니다.

     

     

     

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

    [C/C++] 함수의 호출 방식 - Call by what?  (0) 2019.06.07

    댓글