C++ 복사 생성자(Copy Constructor)

프로그래밍/C++ 2019.10.17 댓글 Plorence

복사 생성자는 어떤 객체를 새로 생성되는 객체에 복사하는 데 사용됩니다.

복사 생성자는 일반적인 대입에 사용되는 것이 아니라 값 전달에 의한 함수 매개변수 전달을 포함한 초기화 작업에 사용됩니다.

 

복사 생성자의 원형

ClassName(const ClassName &);

 

복사 생성자의 호출 시기

복사 생성자는 새로운 객체가 생성되어 같은 종류의 기존 객체로 초기화될 때마다 호출됩니다. 

복사 생성자의 호출 시기는 아래의 코드와 같습니다.

Person Carl();
Person Plorence(Carl); //복사 생성자 호출
Person Plorence =Carl; //복사 생성자 호출
Person Plorence = Person(Carl); //복사 생성자 호출
Person * Plorence = new Person(Carl); //복사 생성자 호출

위 네 가지의 정의 선언이 복사 생성자를 호출합니다.

 

복사 생성자가 하는 일

디폴트 복사 생성자는 static 멤버를 제외한 멤버들을 멤버별로 복사합니다. 이것을 얕은 복사라고 부릅니다.

복사 생성자로 멤버들을 멤버별로 복사했을 때를 깊은 복사라고 부릅니다.

class Person {
private:
    int age;
    int kg;
public:
    Person() {
        age = 0;
        kg = 0;
    }
    Person(int p_age, int p_kg) {
        age = p_age;
        kg = p_kg;
    }
};
int main(void) {
    Person per = Person(15,50);
    Person per1 = per;
}

위 코드에서 복사 생성자를 호출하고 있습니다.

복사 생성자를 호출하면 내부적으로 멤버별 대응 복사를 합니다.

간단히 말하면 오른쪽 피연산자의 각각 멤버의 값이 왼쪽 피연산자 멤버에 복사됩니다.

그림으로 설명(코드가 쪼가리마냥 작네요.)

그 결과 per1 객체의 멤버 age는 15, kg는 50이라는 값을 가지게 됩니다.

하지만 위의 예제 코드에서는 아무 문제가 없습니다.

그런데 동적 메모리나 문자열 같은 '값'을 복사하면 안 되는 것들은 문제가 생기게 됩니다.

(문자열에서 =는 문자열 복사가 아니고 주소를 넣는 겁니다. 그래서 문제가 발생하게 됩니다.)

 

문제가 발생하는 상황

디폴트 복사 생성자의 호출이 끝난 시점부터 문제가 발생합니다.

#include <iostream>
class Person {
private:
    char * name;
public:
    void Show() const {
        std::cout << name;
    }
    ~Person() {
        delete name;
    }
};
int main(void) {
    Person * per = new Person("Plorence");
    per->Show();
    Person * per1 = per;
    delete per;
    per1->Show(); //문제 발생
    return 0;
}

복사 생성자를 정의하지 않았으니 얕은 복사가 됩니다.

그러면 per 객체의 멤버 변수 name의 주소 값이 per1 객체의 멤버 변수 name에 복사됩니다.

즉 둘은 같은 주소 값을 지시하고 있습니다.

한쪽에서 소멸시킨다면, 남은 한쪽에서 소멸된 메모리 영역을 접근하는 치명적인 문제가 발생하게 됩니다.

여기서 또 다른 문제가 있는데 같은 주소값을 지시하니 한쪽에서 값을 바꾸면 다른 한쪽도 영향이 있습니다.

VS2017 디버깅모드

21번째 줄 코드를 실행 후 22번째 줄 코드를 실행하기 전 상태입니다.

아래를 보면 per 객체와 per1의 객체의 멤버 변수가 서로 같은 메모리 공간을 가리킨다는 걸 알 수 있습니다.

위 이미지처럼 컴파일러가 생성한 복사 생성자는 순수 '값'복사 이므로 문제가 되지 않기 위해 새로운 복사 생성자를 정의해줘야 합니다.

 

해결 방법

#include <iostream>
#include <string.h>
class Person {
private:
       char * name;
public:
       Person(const char * str) {
              name = new char[strlen(str) + 1];
              strcpy_s(name, strlen(str)+1, str);
       }
       Person(const Person & per) { //복사 생성자 정의
              name = new char[strlen(per.name) + 1];
              strcpy_s(name, strlen(per.name) + 1, per.name);
       }
       void Show() const {
              std::cout << name;
       }
       ~Person() {
              delete name;
       }
};
int main(void) {
       Person * per = new Person("Plorence");
       per->Show();
       Person * per1 = new Person(*per);
       delete per;
       per1->Show();
       return 0;
}

앞에서의 예제와는 다르게 새로운 공간을 만들고 그 공간에 문자열을 복사하였습니다.

그래서 per 객체의 멤버 변수 name을 할당 해제해도 문제가 없습니다

댓글