Computer Science/C & C++

[C++] Copy Constructor | 복사 생성자

lww7438 2020. 3. 2. 00:16

Copy Constructor

복사 생성자

- 한 객체를 다른 객체로 복사할 때 자동으로 실행되는 생성자이다.
- 일반적인 대입에 사용되는 것이 아닌, 값 전달에 의한(함수 매개변수 전달을 포함한) 초기화 작업에 사용되는 생성자이다.
- 디폴트 복사 생성자는 \(\texttt{static}\) 멤버를 제외한 전 멤버들을 각각 복사 및 대입한다
(이러한 복사 작업을 Shallow Copying; 얕은 복사 또는, Memberwise Copying라 한다.)
- 새로운 객체가 생성될 때마다 \(\texttt{static}\) 데이터 멤버의 값이 변하는 구조의 클래스에서는, 의도에 맞게 \(\texttt{static}\) 데이터 멤버를 수정하는 작업을 복사 생성자에도 구현해놓아야 한다.
(디폴트 복사 생성자는 \(\texttt{static}\) 멤버를 다루지 않기 때문이다.) 

※ 복사 생성자가 호출되는 경우

1. 무조건 호출되는 경우
- 새로운 객체를 기존의 객체로 명시적으로 초기화하는 경우
- 객체 매개변수*가 값으로써 전달되거나 리턴되는 경우 

2. 시스템에 따라 호출할 수도 있고 아닐 수도 있는 경우
- 객체의 복사본을 생성하는 경우
- Call by Value 타입으로 매개변수*가 전달되는 경우
- 어떤 함수에서 리턴값으로써 복사본을 리턴하는 경우
- 컴파일러가 임시 객체를 생성하는 경우
(대개, 임시 객체는 중간 연산 결과를 저장하거나 변수의 초기화, 매개변수 전달 등의 과정에서 생성된다.)

* 객체를 매개변수로 이용할 때, Reference 타입으로 전달하게 되면 실행시간(복사 생성자를 호출하는 시간)과 메모리(새로운 객체를 저장하는 공간)를 개선시킬 수 있다.


Copy Constructor Definition (복사 생성자 정의)

// Ex. 복사 생성자 원형의 일반형

className (const className&);

className objectName_1 = className(objectName_2);
// 위 구문은 아래 구문으로 해석할 수 있다.
className obejctName_2.className(objectName_2);
// 좌변이 복사 생성자를 호출하는 주체, 우변이 매개변수로 전달되는 객체인 것이다.


// 매개변수가 하나이고, 그 클래스 타입의 객체를 받고 있으므로
className objectName_1 = objectName_2;
// 위와 같은 구문은 복사 생성자를 암시적으로 호출하는 구문이 된다.
// 여기서 obejctName_2 또한, className 객체이다.

- 객체를 선언하고 초기화 하는 구문에서, 좌변의 객체가 복사 생성자를 호출하는 주체가 되는 객체이며,
대입할 데이터를 제공하는 우변의 객체가 복사 생성자에 매개변수로 전달되는 객체임을 명시해야 한다.
- 이 점을 이용하여 복사 생성자를 구현하면 된다. 특히 문자열과 같은 포인터 멤버를 복사할 경우, 주솟값을 복사하는 것이 아닌 포인터가 지시하고 있는 값 자체를 복사하는 것이 중요하다. 

// Ex. 복사 생성자 정의

className::className(const className& Ob) {
this->member_1 = Ob.member_1;
this->member_2 = Ob.member_2;
...
}

Call the Copy Constructor (복사 생성자가 호출되는 경우)

// Ex. 복사 생성자가 호출되는 경우

className ob1(ob);          // 단, ob는 기존에 정의되어 있던 className 객체이다.
className* ob4 = new className(ob);


// 아래 두 구문은, C++ 시스템에 따라
// 복사 생성자를 사용하여 ob2, ob3 객체를 직접 생성할 수도 있고,
// 복사 생성자로 임시 객체를 생성한 후에 대입시킬 수도 있다.
className ob2 = ob;
className ob3 = className(ob);

Shallow Copy - Deep Copy (얕은 복사 - 깊은 복사)

- 얕은 복사와 깊은 복사는 C++의 문법이나 어떤 키워드로 구현하는 것이 아니라, 복사 생성자의 내부 구현 방식을 보고 어떤 방식으로 작동되는지를 논리적으로 확인해야 한다.

Shallow Copy (얕은 복사)
- \(\texttt{static}\) 멤버를 제외한 모든 데이터 멤버들이 저장하고 있는 값을 다른 객체에 복사하는 방식이다.
- 얕은 복사 방식의 복사 생성자에서는 포인터 변수가 갖고 있는 값(주솟값) 또한, 다른 객체의 멤버에 그대로 복사된다.
(따라서, 대입 연산자를 기준으로 좌변 객체의 포인터 멤버와 우변 객체의 포인터 멤버가 서로 같은 메모리를 가리키게 된다.)
- 특히, \(\texttt{new}\)로 할당받은 메모리를 얕은 복사를 통해 여러 객체에서 가리키는 상황에서, 파괴자와 같은 다른 메서드에서 두 번이상 \(\texttt{delete}\)로 해제하게 되면 오류가 발생하게 된다.

Deep Copy (깊은 복사)
- 객체의 포인터 멤버들이 이전 객체(대입 연산자를 기준으로 우변에 있는 객체)의 주솟값을 그대로 들여오는 것이 아닌, 독립된 주솟값을 갖게끔 복사 생성자를 구현하는 것으로 깊은 복사를 구현할 수 있다.

// Ex. Deep Shallow 방식의 복사 생성자 구현 예시

class StringBad {
private:
    int len;
    char* str
public:
    StringBad(const StringBad&);
    ...
};

StringBad::StringBad(const StringBad& st) {
    len = st.len;
    str = new char[len + 1];        // 문자열을 단순하게 복사, 대입하지 않는다.
    std::strcpy(str, st.str);       // 새로 생긴 객체도 자신만의 메모리를 대입받고, 값만 복사받는다.
}