1. 복사 생성자, 복사 대입 연산자

class Knight
{
public:


public:
    int _hp = 100;
};

int main()
{
    Knight knight; // 기본 생성자
	knight._hp = 200;
    
    Knight knight2 = knight;
//~~

이 코드는 복사 생성자를 이용하여 knight2 객체를 생성하고

//~~
    Knight knight3; // 기본 생성자
    knight3 = knight;

    return 0;
}

이 코드는 기본 생성자를 호출하고 복사 대입 연산자를 이용하여 knight3 객체를 생성한다.

이처럼 복사 생성자와 복사 대입 연산자는 임의적으로 만들지 않으면 컴파일러가 암시적으로 생성해준다.

 

2. 얕은 복사(Shallow Copy)

class Pet
{
public:
    Pet() // 기본 생성자
    {}
    ~Pet() // 소멸자
    {}
    Pet(const Pet& pet) // 복사 생성자
    {}
};

Knight 클래스가 Pet을 가지고 있다고 하자.

class Knight
{
public:


public:
    int _hp = 100;
    Pet _pet;
};

이렇게 코드를 작성하면 Knight의 객체가 생성되면 _pet도 생성되고, Knight의 객체가 소멸하면 _pet도 소멸되게 된다.

(_pet의 생성자와 소멸자도 올바르게 호출됨)

만약 Pet의 데이터에 큰 데이터가 저장되어 있었다면 Knight의 객체 또한 그만큼의 데이터를 저장하게 된다.

또한 Pet의 자식인 클래스(토끼, 거북이 등)을 넣으려면 코드를 수정해야된다.

따라서 Knight 클래스는 펫의 포인터를 들고있도록 수정한다.

class Knight
{
public:


public:
    int _hp = 100;
    Pet* _pet;
};

 

Pet 클래스의 객체인 pet을 생성하고 knight 객체가 pet을 가지게 한다.

int main()
{
    Pet* pet = new Pet();

    Knight knight; // 기본 생성자
    knight._hp = 200;
    knight._pet = pet;

그 다음 knight를 복사한 knight2와 knight3를 확인하면

둘 다 knight와 동일한 pet을 가지고 있게 된다.

 

즉 얕은 복사는 멤버 데이터를 비트열 단위로 동일하게(그대로) 복사한다.

 

3. 깊은 복사(Deep Copy)

멤버 데이터가 참조(주소) 값 이라면 데이터를 새로 만들어서 복사한다.

즉 복사된 knight2와 knight3는 hp와 pet을 각기 다르게 가진다.

class Knight
{
public:
    Knight()
    {
        _pet = new Pet();
    }

    ~Knight()
    {
        delete _pet;
    }

    Knight(const Knight& knight) // 복사 생성자, Knight knight2 = knight;
    {
        _hp = knight._hp;
        _pet = new Pet(*(knight._pet)); 
    }

    Knight& operator=(const Knight& knight) // 복사 대입 연산자, knight3 = knight2; 에서의 등호
    {
        _hp = knight._hp;
        _pet = new Pet(*(knight._pet));
        return *this
    }
//~~

위 코드에서 Knight 클래스의 객체를 만들면 멤버 변수 _pet은 새로운 Pet의 객체를 만들고 각 knight 객체의 _pet 주소를 저장하게 한다.

 

4. 암시적 vs 명시적

(1) 복사 생성자

- 암시적 복사 생성자

1. 부모 클래스의 복사 생성자 호출

2. 멤버 클래스(포인터가 아닌 일단 클래스가 멤버 형태로 있을 때)의 복사 생성자 호출

3. 멤버가 기본 타입이면 얕은 복사

 

- 명시적 복사 생성자

1. 부모 클래스의 기본 생성자 호출

2. 멤버 클래스의 기본 생성자 호출

Player라는 새로운 클래스를 만들고 Knight 클래스가 Player를 상속받게 한다음

복사 생성자를 명시적으로 해주면 기본 생성자를 호출하게 되므로

각 knight 객체들이 기본 생성자의 값을 따라가게 된다.

따라서 복사 생성자도 Player(knight)를(펫 또한) 상속한다는 것을 명시적으로 적어야 한다.

    Knight(const Knight& knight) : Player(knight), _pet(knight._pet)
    {
        _hp = knight._hp;
        _pet = new Pet(*(knight._pet));

 

(2) 복사 대입 연산자

- 암시적 복사 대입 연산자

1. 부모 클래스의 복사 대입 연산자 호출

2. 멤버 클래스의 복사 대입 연산자 호출

3. 멤버가 기본 타입이면 얕은 복사

 

- 명시적 복사 대입 연산자

자동으로 해주는 것이 없으므로 별도로 처리해줘야 함.

각 knight 객체들이 기본 생성자의 값을 따라가게 된다.

따라서 복사 대입 연산자 안에 각 값을 직접 저장하게 지정해줘야 한다.

    Knight& operator=(const Knight& knight)
    {
        Player::operator=(knight);

        _hp = knight._hp;
        _pet = knight._pet;
        return *this;

객체를 복사한다는 것은 두 객체의 값을 그대로 하나 더 만든다는 것이므로 일반적으로 얕은 복사 방식으로 동작한다.

따라서 명시적으로 복사하면 프로그래머가 책임을 지게 된다.

'기초 C++ 스터디 > 객체지향' 카테고리의 다른 글

6-7. 전방 선언 vs 포함(#include)  (0) 2023.06.05
6-5. 캐스팅  (0) 2023.06.02
6-3. 형(Type) 변환 : 포인터 (2)  (0) 2023.06.01
6-2. 형(type) 변환 (1)  (0) 2023.05.31
6-1. 동적 할당  (0) 2023.05.31