플로렌스라는 개발자

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

프로그래밍/C++ 2019.10.17 댓글 0 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을 할당 해제해도 문제가 없습니다

    댓글

    0