본문 바로가기

교육 노트/C++ 심화강의

[C++ 때려잡기] C++ 심화강의 4 복사 생성자와 깊은 복사

2018/08/27 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 1 객체 지향 프로그래밍과 클래스

2018/08/27 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 2 접근 한정자

2018/08/27 - [교육 노트/C++ 심화강의] - [C++ 때려잡기] C++ 심화강의 3 생성자와 소멸자


이전 강의에서 생성자를 배웠다.

특히 생성자에서 파라미터를 받아 객체를 생성하면서 각종 초기화 과정을 진행 해 줄 수 있었는데

생성자에서는 복사생성자라는 특수한 생성자가 하나 존재한다.



1. 복사 생성자

자기 자신과 같은 형태의 객체를 인자로 받을수 있는 생성자를 복사생성자라고 한다.


복사 생성자는 같은 클래스를 인자로 받는 생성자이며 대부분 인자로 받은 클래스의 내용을 카피하는데 사용하기 때문에 복사생성자라고 한다.

#include <iostream>
using namespace std;

class Character
{
public:
    Character(int _hp)
    {
        hp = _hp;
    }

    Character(Character& _Character)
    {
        hp = _Character.hp;
    }
private:
    int hp;

public:
    int getHp();

};


int Character::getHp()
{
    return hp;
}


int main()
{
    Character mario(100);           //파라미터를 받는 생성자
    cout << mario.getHp() << endl;
    Character luigi(mario);         //복사 생성자 사용
    cout << luigi.getHp() << endl;

    return 0;
}

다음과 같은 방식으로 사용한다

루이지는 마리오를 파라미터로 받아

마리오와 같은hp 로 자신을 초기화했다


또한 복사생성자에서 받는 파라미터인 _Character의 클래스가 Chararcter이다.

따라서 같은 클래스 이므로 _Character.hp 와 같이 _Character의 private 변수에 접근이 가능하다.



그럼 여기서 복사생성자가 없으면 어떻게 될까?


#include <iostream>
using namespace std;

class Character
{
public:
    Character(int _hp)
    {
        hp = _hp;
    }

private:
    int hp;

public:
    int getHp();

};


int Character::getHp()
{
    return hp;
}


int main()
{
    Character mario(100);           //파라미터를 받는 생성자
    cout << mario.getHp() << endl;
    Character luigi(mario);         //복사 생성자 사용
    cout << luigi.getHp() << endl;

    return 0;
}


아주 잘 동작한다.


그 이유는 복사 생성자를 사용자가 만들어 주지 않으면 자동으로

클래스에 있는 모든 변수를 파라미터로 받은 객체의 변수와 1대1로 자동으로 카피하는 디폴트 복사 생성자가 만들어지기 때문이다.




    Character(Character& _Character)
    {
        hp = _Character.hp;
    }

과 똑같이 작동하는 복사 생성자가 자동으로 만들어 진다는 소리다.




대부분의 복사생성자는 파라미터로 받은 객체와 똑같이 값을 초기화 하기 위해쓰는데

그럼 똑같이 값을 초기화 할때를 제외하고 복사생성자는 굳이 만들 필요가 없는것일까?



2. 얕은 복사와 깊은 복사 (shallow copy, deep copy)

클래스 내부에 변수로

포인터 값이 있다고 해보자

마리오 객체의 포인터는 동적할당으로 A라고 저장된 메모리를 가르키고 있다.

루이지 객체는 마리오 객체를 카피 할것이다.

복사가 끝나면 우리가 원하는 상황은 위 그림과 같을것이다.


루이지의 포인터가 가르키는 값을 바꾸면

이런식으로 작동하기를 원할것이다.


그러나 중요한점은 p가 포인터라는것이고 포인터라는것은 주소값을 가르킨다.

즉 디폴트 복사 생성자는 해당 포인터값조차 그냥 복사해버릴 테고

그렇게 되면


이렇게 복사가 되버릴것이다!

포인터는 주소값이기때문에 같은 메모리의 주소값을 가르키는 포인터가 2개 생긴것이다.

여기서 루이지에서 값을 바꿔버리면


망할 루이지에서 바꿧는데 마리오까지 바뀌어버렸다


여기서 마리오 객체가 이제 쓸모가 없어져서 지워지면

망할 마리오 소멸자에서 동적할당한것을 delete해버려서 루이지가 가르키고있던 값도 소멸해버렸다.


이러한 복사방법이 얕은복사 (shallow copy) 이다

그냥 주소값이든 그냥 값이든 1대1로 고대로 복사하는것이 얕은복사인데

디폴트 복사 생성자는 이러한 얕은 복사를 해버린다.


우리가 원래 원했던 복사방식이 깊은 복사 (deep copy) 로 포인터가 있으면 포인터가 가르키고 있는 값을 추적하여 해당 값을 복사하여 할당하고 이 값을 내 포인터가 가르키게하는 방식이다.

해당방식은 기본으로 제공해주지 않기때문에

개발자가 스스로 프로그래밍을 해주어야한다.

그래서 우리가 직접 복사생성자를 만들수 있도록 한것이다.

728x90