얕은 복사 vs 깊은 복사 1

 

인프런 Rookiss님의 'Part1: C++ 프로그래밍 입문' 강의를 기반으로 정리한 필기입니다. 
😎[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part1: C++ 프로그래밍 입문 강의 들으러 가기!

 

 

 

목차

     

     


     

     

    기본

     

    #include <iostream>
    using namespace std;
    
    class Pet {
    public:
        Pet() 	{ cout << "Pet()" << endl; }
        ~Pet() 	{ cout << "~Pet()" << endl; }
        Pet(const Pet& pet) { cout << "Pet(const Pet& pet)" << endl; }
    };
    
    class Knight{
    public:
        int _hp = 100;
        Pet* _pet;
    };
    
    int main()
    {
        Pet* pet = new Pet();
    
        Knight knight;  // 기본 생성자
        knight._hp = 200;
        knight._pet = pet;
    
        Knight knight2 = knight;  // 복사 생성자
        Knight knight2(knight);   // 복사 생성자. 위와 동일. 표기방법 차이
    
        Knight knight3;   // 기본 생성자
        knight3 = knight; // 복사 대입 연산자
    
        return 0;
    }

    Knight 클래스에 Pet 클래스를 들고 있다.

     

    [복사 생성자] + [복사 대입 연산자]

    둘 다 안 만들어주면 컴파일러가 '암시적으로' 만들어준다

     

    중간 결론) 컴파일러가 알아서 잘 만들어준다?

    수고하세요~ 다음 주제 넘어갈까요? << NO

     


     

     

    얕은 복사 Shallow Copy

     

    #include <iostream>
    using namespace std;
    
    class Pet {
    public:
        Pet() 	{ cout << "Pet()" << endl; }
        ~Pet() 	{ cout << "~Pet()" << endl; }
        Pet(const Pet& pet) { cout << "Pet(const Pet& pet)" << endl; }
    };
    
    class Knight{
    public:
        int _hp = 100;
        Pet* _pet;
    };
    
    int main()
    {
        Pet* pet = new Pet();
    
        Knight knight;  // 기본 생성자
        knight._hp = 200;
        knight._pet = pet;
    
        Knight knight2 = knight;  // 복사 생성자
        Knight knight2(knight);   // 복사 생성자. 위와 동일. 표기방법 차이
    
        Knight knight3;   // 기본 생성자
        knight3 = knight; // 복사 대입 연산자
    
    	// knight1, knight2, knight3 모두 같은 pet을 공유하고 있다.
        
        return 0;
    }

     

    멤버 데이터를 비트열 단위로 '똑같이' 복사 (메모리 영역 값을 그대로 복사)

     

    포인터는 주소값 바구니 -> 주소값을 똑같이 복사 -> 동일한 객체를 가리키는 상태가 됨

    Stack : Knight1 [ hp 0x1000 ] -> Heap 0x1000 Pet[   ]

    Stack : Knight2 [ hp 0x1000 ] 

    Stack : Knight3 [ hp 0x1000 ] 

     

     knight1, knight2, knight3 모두 같은 pet을 공유하고 있다.

    _pet 메모리의 주소가 같다

     

     

    ※ 얕은 복사가 문제가 되는 상황

    Knight 안에 생성자와 소멸자가 추가된 상황

    #include <iostream>
    using namespace std;
    
    class Pet {
    public:
        Pet() 	{ cout << "Pet()" << endl; }
        ~Pet() 	{ cout << "~Pet()" << endl; }
        Pet(const Pet& pet) { cout << "Pet(const Pet& pet)" << endl; }
    };
    
    class Knight{
    public:
        Knight() { _pet = new Pet(); }
        ~Knight() { delete _pet; }
    
    public:
        int _hp = 100;
        Pet* _pet;
    };
    
    int main()
    {
        Pet* pet = new Pet();
    
        Knight knight;  // 기본 생성자
        knight._hp = 200;
        knight._pet = pet;
    
        Knight knight2 = knight;  // 복사 생성자
        Knight knight2(knight);   // 복사 생성자. 위와 동일. 표기방법 차이
    
        Knight knight3;   // 기본 생성자
        knight3 = knight; // 복사 대입 연산자
    
        return 0;
    }

     

    컴파일 시 다음과 같은 화면이 뜨며 컴파일 되지 않는다.

     

     

     

     

     

     

    Stack : Knight1 [ hp 0x1000 ] -> Heap 0x1000 Pet[   ]

    Stack : Knight2 [ hp 0x1000 ] 

    Stack : Knight3 [ hp 0x1000 ] 

     

    Knight 1, 2, 3이 같은 Pet을 가리키고 있기 때문에 delete _pet이라는 코드가 3번 실행된다.

    이런 경우, double free 문제가 발생한다. 여러번 삭제해서 문제가 되는 것이다.


     

     

    깊은 복사 Deep Copy

     

    #include <iostream>
    using namespace std;
    
    class Pet {
    public:
        Pet() 	{ cout << "Pet()" << endl; }
        ~Pet() 	{ cout << "~Pet()" << endl; }
        Pet(const Pet& pet) { cout << "Pet(const Pet& pet)" << endl; }
    };
    
    class Knight{
    public:
        Knight() { _pet = new Pet(); }
    
        Knight(const Knight& knight){ // 명시적 복사 생성자
            _hp = knight._hp;
            _pet = new Pet(*(knight._pet)); // 깊은 복사
        }
    
        Knight& operator=(const Knight& knight){ // 명시적 복사 대입 연산자
            _hp = knight._hp;
            _pet = new Pet(*(knight._pet)); // 깊은 복사
            return *this;
        }
    
        ~Knight() { delete _pet; }
    
    public:
        int _hp = 100;
        Pet* _pet;
    };
    
    int main()
    {
        Pet* pet = new Pet();
    
        Knight knight;  // 기본 생성자
        knight._hp = 200;
        knight._pet = pet;
    
        Knight knight2 = knight;  // 복사 생성자
        Knight knight2(knight);   // 복사 생성자. 위와 동일. 표기방법 차이
    
        Knight knight3;   // 기본 생성자
        knight3 = knight; // 복사 대입 연산자
    
        return 0;
    }

     

    ' 복사 생성자 '와 ' 복사 대입 연산자 '를 명시적으로 만들어준다!

     

    멤버 데이터가 참조(주소) 값이라면, 데이터를 새로 만들어준다 (원본 객체가 참조하는 대상까지 새로 만들어서 복사)

     

    포인터는 주소값 바구니 -> 새로운 객체를 생성 -> 상이한 객체 가리키는 상태가 됨 

    Stack : Knight1 [ hp 0x1000 ] -> Heap 0x1000 Pet[   ]

    Stack : Knight2 [ hp 0x2000 ] -> Heap 0x2000 Pet[   ]

    Stack : Knight3 [ hp 0x3000 ] -> Heap 0x3000 Pet[   ]

     

    _pet 메모리의 주소가 다르다.


     

     

    전체코드

     

    더보기
    #include <iostream>
    using namespace std;
    
    // 얕은 복사 vs 깊은 복사
    
    class Pet
    {
    public:
        Pet()
        {
            cout << "Pet()" << endl;
        }
        ~Pet()
        {
            cout << "~Pet()" << endl;
        }
        Pet(const Pet& pet)
        {
            cout << "Pet(const Pet& pet)" << endl;
        }
    
    };
    
    class RabbitPet : public Pet
    {
    
    };
    
    class Knight
    {
    public:
        Knight()
        {
            _pet = new Pet();
        }
    
        Knight(const Knight& knight)
        {
            _hp = knight._hp;
            _pet = new Pet(*(knight._pet)); // 깊은 복사
        }
    
        Knight& operator=(const Knight& knight)
        {
            _hp = knight._hp;
            _pet = new Pet(*(knight._pet)); // 깊은 복사
            return *this;
        }
    
        ~Knight()
        {
            delete _pet;
        }
    
    public:
        int _hp = 100;
        Pet* _pet;
    };
    
    
    int main()
    {
        Pet* pet = new Pet();
    
        Knight knight;  // 기본 생성자
        knight._hp = 200;
        knight._pet = pet;
    
        Knight knight2 = knight;  // 복사 생성자
        //Knight knight3(knight);
    
        Knight knight3;   // 기본 생성자
        knight3 = knight; // 복사 대입 연산자
    
        // [복사 생성자] + [복사 대입 연산자]
        // 둘 다 안 만들어주면 컴파일러가 '암시적으로' 만들어준다
    
        // 중간 결론) 컴파일러가 알아서 잘 만들어준다?
        // 수고하세요~ 다음 주제 넘어갈까요? << NO
    
        // [ 얕은 복사 Shallow Copy ]
        // 멤버 데이터를 비트열 단위로 '똑같이' 복사 (메모리 영역 값을 그대로 복사)
        // 포인터는 주소값 바구니 -> 주소값을 똑같이 복사 -> 동일한 객체를 가리키는 상태가 됨
        // Stack : Knight1 [ hp 0x1000 ] -> Heap 0x1000 Pet[   ]
        // Stack : Knight2 [ hp 0x1000 ] 
        // Stack : Knight3 [ hp 0x1000 ] 
    
    
        // [ 깊은 복사 Deep Copy ]
        // 멤버 데이터가 참조(주소) 값이라면, 데이터를 새로 만들어준다 (원본 객체가 참조하는 대상까지 새로 만들어서 복사)
        // 포인터는 주소값 바구니 -> 새로운 객체를 생성 -> 상이한 객체 가리키는 상태가 됨 
        // Stack : Knight1 [ hp 0x1000 ] -> Heap 0x1000 Pet[   ]
        // Stack : Knight2 [ hp 0x2000 ] -> Heap 0x2000 Pet[   ]
        // Stack : Knight3 [ hp 0x3000 ] -> Heap 0x3000 Pet[   ]
    
        return 0;
    }