[C++] 복사 생성자, 복사 대입 연산자 (+깊은 복사)

클래스 생성 시 컴파일러가 자동으로 생성해주는 함수는 생성자, 소멸자, 복사 생성자, 복사 대입 연산자 이렇게 4가지가 있으며 이번 포스팅에서 다룰 내용은 복사 대상자와 복사 대입 연산자입니다. 생성자나 소멸자에 대해 궁금하시다면 아래 글의 생성자와 소멸자 부분을 참고해주세요.

[C++] 클래스(Class) 사용법 & 예제 총정리

     

    복사 생성자와 복사 대입 연산자란?

    복사 생성자는 객체의 복사본을 생성할 때 호출되는 생성자입니다. 생성자나 소멸자와 마찬가지로 클래스를 작성할 때 복사 생성자를 생략하면 디폴트 생성자처럼 컴파일러가 알아서 만들어줍니다. 

     

    복사 대입 연산자는 같은 타입의 객체를 이미 생성되어 있는 객체에 값을 복사할 때 사용됩니다. 마찬가지로 따로 정의를 해주지 않을 경우 컴파일러가 알아서 생성해줍니다.

     

    예제

    #include <iostream>
    using namespace std;
    
    class A {
    public:
        A(){
            cout << "생성자" << endl;
        }
        
        A(const A& rhs) {
            cout << "복사 생성자" << endl;
        }
        
        A& operator=(const A& rhs){
            if (this != &rhs){ //다를때만 복사 수행
                 cout << "복사 대입연산자" << endl;
            }
            return *this;
        }
    };
    
    void func_Value(A a){}; // 복사생성자 호출
    void func_Reference(A &a){}; // 복사생성자 호출 안됨.
    void func_Pointer(A* a){}; //복사생성자 호출 안됨.
    
    int main()
    {
        //객체
        cout << "객체" << endl;
        A a1; // 생성자만 호출
        A a2; //생성자만 호출
        A a3 = a1; // 복사생성자 호출됨(초기화되는 시점)
        a1 = a2; //복사 대입 연산자 호출
        
        //함수
        cout << endl<< "함수" << endl;
        func_Value(a1); //복사 생성자 호출됨
        func_Reference(a1); //복사 생성자 호출안됨
        func_Pointer(&a1); //복사 생성자 호출안됨
        
        return 0;
    }//객체가 생성되는 블록이 끝나는 시점에 소멸자 호출
    

    위의 예제를 보시면 복사연산자와 복사 대입 연산자의 차이점에 대해 쉽게 확인이 가능합니다. 쉽게 정의하자면 객체를 생성함과 동시에 다른 객체를 복사하면 복사 생성자가 호출되고 이미 생성되어 있는 객체에 다른 객체를 복사하면 복사 대입 연산자가 호출됩니다. 위의 예제에서 복사 대입 연산자의 return형을 A& 이렇게 reference 형을 return 하는 이유는 a1 = a2 = a3 이런식으로 연속으로 대입 연산자가 호출이 가능하도록 하기 위함입니다.

     

    단 이렇게 따로 정의를 하지 않고 컴파일러가 알아서 생성해주는 디폴트 복사 생성자, 디폴트 복사 대입 연산자는 모두 얕은 복사를 합니다.

     

    얕은 복사란?

    얕은 복사는 객체가 가진 멤버들의 값을 새로운 객체로 복사하는데 만약 복사 대상객체가 참조타입의 멤버를 가지고 있다면 참조값만 복사가 됩니다. 그렇기에 이 참조타입의 값이 변경될 경우 해당 객체로 복사한 모든 객체들의 값이 일괄적으로 바뀌는 문제가 발생 합니다.

     

    얕은 복사의 문제점

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    
    using namespace std;
    
    class Person{
    public:
        int age;
        char* name;
    
        Person(int _age, const char* _name){
            age = _age;
            name = new char[strlen(_name) + 1];
            strcpy(name, _name);
        }
        
        void infoPerson(){
            cout << "이름: " << name << endl;
            cout << "나이: " << age << endl;
        }
    };
    
    void main(){
        Person A(20, "홍길동");
        Person B = A;
        
        B.age = 30;
        strcpy(B.name, "이순신");
        
        A.infoPerson();
        B.infoPerson();
    }
    

    위의 예제를 보시면 A라는 Person객체와 B라는 Person객체를 각각 생성하여 B라는 Person객체의 이름을 이순신이라고 바꾸었는데 A라는 Person객체의 이름까지 같이 변경된것을 확인하실 수 있습니다.

     

    해당 문제는 Person이라는 객체를 복제하여 사용할 때 얕은 복사로 인해 char* name을 여러 객체에서 같이 참조하여 발생하는 문제입니다.

     

    복사 생성자를 활용하여 깊은 복사하기

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    
    using namespace std;
    
    class Person{
    public:
        int age;
        char* name;
    
        Person(int _age, const char* _name){
            age = _age;
            name = new char[strlen(_name) + 1];
            strcpy(name, _name);
        }
        
        Person(const Person& p){ //복사 생성자로 깊은 복사하기
            age = p.age;
            name = new char[strlen(p.name) + 1];
            strcpy(name, p.name);
        }
        
        void infoPerson(){
            cout << "이름: " << name << endl;
            cout << "나이: " << age << endl;
        }
    };
    
    void main(){
        Person A(20, "홍길동");
        Person B = A;
        
        B.age = 30;
        strcpy(B.name, "이순신");
        
        A.infoPerson();
        B.infoPerson();
    }
    

    복사 생성자를 활용하여 복사할때마다 새로운 char객체를 만들어줌으로써 아까와 같은 문제를 해결할 수 있습니다. 위의 출력값을 보시면 B의 객체의 name을 변경했음에도 아까와 같이 A의 name값도 함께 변경되지 않은 것을 확인하실 수 있습니다.

     

    위의 그림과 같이 Person객체마다 각각의 name을 가지고 있기 때문입니다.

    댓글(0)

    Designed by JB FACTORY