C++/C++ 기본

복사 생성자

검정비니 2018. 7. 27. 18:30
728x90
반응형

복사 생성자




1. 얕은 복사와 깊은 복사



복사란 원본과 동일한 별개의 사본을 만드는 것이다.


복사를 자세히 살펴보면 얕은 복사(shallow copy)와 깊은 복사(deep copy)로 구분할 수 있다.


어린이가 장난감을 가지고 있다고 하자. 얕은 복사의 경우에는 어린이만 복사를 하고 장난감은 복사를 하지 않는다. 얕은 복사 후, 두 어린이는 장난감을 서로 자기 것이라고 아는 충돌이 생긴다. 이와 달리 깊은 복사는 원본이 소유한 모든 것까지 복사한다. 깊은 복사가 이루어지면 어린이가 소유한 장난감도 복사되므로 얕은 복사에서 생기는 충돌은 발생하지 않는다.



- 객체의 얕은 복사와 깊은 복사


얕은 복사와 깊은 복사는 C++의 객체 복사에도 존재한다. 어떤 복사 방식을 사용하느냐에 따라 복사 결과에 심각한 차이가 발생한다.


id와 name 멤버를 가진 Person 클래스가 있다고 하자. id는 int형 변수이고, name은 char 타입 포인터이다. 원본 Person 객체의 id는 1이고 name 포인터는 "Kitae" 문자열을 가진 동적 할당 배열을 가리키고 있다고 하자. 이 상태에서 얕은 복사가 이루어지면 원본 객체의 id와 name 멤버는 현재 상태 그대로 사본 객체에 복사되므로, 사본의 name은 원본의 name 메모리를 공유하게 된다. 그러나 깊은 복사는 원본의 name 포인터가 가리키는 메모리까지 복사하여 원본과 사본의 name은 별개의 메모리를 가리키므로, 완전한 복사가 이루어진다.



- 객체의 얕은 복사 문제점


얕은 복사의 경우 포인터 변수의 주소를 두 객체가 공유하기 때문에, 사본 객체의 포인터 멤버의 값을 변경하게 되면 원본 객체에도 영향을 주게 된다. 이러한 문제는 많은 경우 개발자가 인지하지 못한 상태에서 발생하기 때문에 오류를 찾아내고 수정하는데 많은 시간이 걸리기도 한다. 가능하면 얕은 복사가 일어나지 않도록 주의하고, 얕은 복사를 사용할 때에는 메모리 주소의 공유와 관련된 문제를 잘 신경쓰도록 하자.




2. 복사 생성 및 복사 생성자



- 복사 생성자 선언


복사 생성은 객체가 생성될 때 원본 객체를 복사하여 생성되는 경우로서, C++ 복사 생성 시에만 실행되는 특별한 복사 생성자(copy constructor)가 있다. 복사 생성자는 다음과 같이 선언한다.


i.e.

class ClassName {

ClassName(ClassName& c); //복사 생성자

};


복사 생성자의 매개 변수는 오직 하나이며, 자기 클래스에 대한 참조로 선언된다. 또한 복사 생성자는 클래스에 오직 한 개만 선언할 수 있다.



- 복사 생성자 실행


우선, 지금 다루는 내용이 치환 연산(=)을 통한 객체 복사가 아니라, 복사 생성이라는 점을 분명히 알기를 바란다.


다음 예제를 통해서 복사 생성자의 실행 방법에 대해서 알아 보도록 하자.


i.e.

ClassName src(); //일반적인 생성자 사용

ClassName dest(src); //src 객체를 복사하여 dest 객체 생성. 복사 생성자 ClassName(ClassName& c) 호출


컴파일러는 dest 객체가 생성될 때 보통 생성자 대신 복사 생성자 ClassName(ClassName& c)가 호출되도록 컴파일한다.



- 디폴트 복사 생성자


다음 Circle 클래스는 복사 생성자를 가지고 있지 않다.


i.e.

class Circle {

int radius;

public:

Circle(int r);

double getArea();

}


그렇다면 위의 클래스로 복사 생성자를 호출하면 컴파일 오류가 발생할까?


컴파일러는 오류로 처리하는 대신 다음과 같은 디폴트 복사 생성자(default copy constructor)를 묵시적으로 삽입하고 이 생성자를 호출하도록 컴파일 한다.


Circle::Circle(Circle& c) { //디폴트 복사 생성자

this.radius = c.radius; //원본 객체 c의 각 멤버를 사본(this)에 복사한다.

}


컴파일러가 삽입하는 디폴트 복사 생성자 코드는 소위 얕은 복사를 실행하도록 만들어진 코드이다. 컴파일러가 삽입한 복사 생성자는 우너본 객체의 모든 멤버를 일대일로 사본(this)에 복사하도록 구성된다. 앞의 Circle 클래스는 멤버 변수가 radius 하나뿐이므로 radius 값만 복사하는 코드로 구성되었다.


디폴트 복사 생성자든 개발자가 작성한 것이든 얕은 복사는 앞서 말했던 공유의 문제가 발생할 소지를 안고 있다.




3. 얕은 복사 생성자의 문제점



포인터 타입의 멤버 변수가 없는 클래스의 경우, 얕은 복사는 전혀 문제가 없다. 모든 멤버 변수를 일대일로 복사해도 공유의 문제가 발생하지 않기 때문이다. 그러나 클래스가 포인터 멤버 변수를 가지고 있는 경우, 원본 객체의 포인터 멤버 변수가 사본 객체의 포인터 멤버 변수에 복사되면, 이 둘은 같은 메모리를 가리키게 되어 심각한 문제를 야기한다.


예를 들어 보자. 포인터 멤버에 동적 메모리를 할당받은 원본 객체의 얕은 복사가 이루어지면, 원본 객체가 할당받은 메모리를 사본 객체가 공유하게 된다.

반응형

'C++ > C++ 기본' 카테고리의 다른 글

대입 연산자  (0) 2018.07.28
묵시적 복사 생성  (0) 2018.07.28
참조와 함수  (0) 2018.07.27
객체 치환 및 객체 리턴  (0) 2018.07.26
함수 호출 시 객체 전달  (0) 2018.07.26