C, C++

29. 복사 생성자

Yongs12 2023. 6. 19. 23:38

C++에서 복사 생성자는 동일한 클래스의 기존 객체 복사본으로 새 객체를 만드는 데 사용되는 유형의 생성자이다.
이때 깊은 복사(Deep Copy)와 얕은 복사(Shallow Copy)의 개념이 등장한다.


Shallow Copy(얕은 복사) 예시

int main()
{
    int* pA = new int(5);
    int* pB = nullptr;

    // Shallow Copy(얕은 복사)
    pB = pA;
    
    delete pA;
    
    // 얕은 복사를 한 경우 pA를 지우면 pB는 pA를 가리키고 있는 포인터일 뿐이기에 
    // 삭제된 데이터를 지우려고 하기에 에러가 뜬다.
    delete pB;

    return 0;
}


Deep Copy(깊은 복사) 예시

int main()
{
	int* pA = new int(5);

	// 그대로 복사가 아닌 새롭게 메모리를 할당해준다.
	int* pB = new int(*pA);

	delete pA;
	delete pB;

	return 0;
}



포인터로 가리키기만 하는 것은 Shallow 원본 데이터를 초기화로 주는 새로운 객체를 만들면 Deep Copy

 




복사 생성자 예시

#include <iostream>

class CTest
{
public:
    CTest()
    {
        std::cout << "CTest() 생성자 호출" << std::endl;
    }
    
    ~CTest()
    {
        std::cout << "~CTest() 소멸자 호출" << std::endl;
    }

};

int main()
{
    CTest a;

    // 복사 생성자 호출 컴파일러가 자동으로 생성
    CTest b(a);
    return 0;
}

 

CTest b(a)에서 복사 생성자는 컴파일러가 자동으로 생성하지만 아무런 기능이 없이 호출된다. 하지만 소멸자는 똑같이 객체를 만들었기 때문에 호출 된다.



복사 생성자는 컴파일러가 아닌 본인이 직접 만들어야 할 때가 있는데
포인터를 사용할 경우 만들어줘야 한다.

#include <iostream>

class CTest
{
    // 포인터 변수를 사용할 시
    int* pA;
public:
    CTest()
    {
        std::cout << "CTest() 생성자 호출" << std::endl;
        pA = new int;
    }
    
    ~CTest()
    {
        std::cout << "~CTest() 소멸자 호출" << std::endl;
        delete pA;
    }

};

int main()
{
    CTest a;

    // 복사 생성자 호출
    CTest b(a);
    return 0;
}

복사 생성자로 복사한 객체에도 똑같이 동적 할당된 포인터가 있고 소멸자에서 해제가 되기 때문에 중복 해제가 된다.

 

소멸자가 2번 호출 되었기 때문에 이미 없는 메모리 공간을 삭제하려고 하기 때문에 Error가 발생


위와 같이 포인터 변수에 메모리 동적할당을 할 경우 원본 객체와 복사 생성된 객체에서 둘 다 소멸자 호출 시 동적 할당된 메모리를 해제하기 때문에 Error가 발생한다.


동적 할당된 객체를 복사할 경우 새롭게 메모리를 동적 할당 해야 한다.

#include <iostream>

class CTest
{
    int* pA;
public:
    CTest()
    {
        std::cout << "CTest() 생성자 호출" << std::endl;
        pA = new int;
    }
    
    // 직접 복사 생성자를 만들어 준다. 이 때 const 키워드를 쓰는 이유는
    // 원본을 변경안하고 읽어들이기만 하기 위해서다.
    CTest(const CTest& _pA)
    {
        std::cout << "CTest(CTest& _pA) 복사 생성자 호출" << std::endl;
        // 복사되는 객체에 새롭게 메모리를 동적 할당해주어야 한다.
        this->pA = new int(*_pA.pA);
    }

    ~CTest()
    {
        std::cout << "~CTest() 소멸자 호출" << std::endl;
        delete pA;
    }

};

int main()
{
    CTest a;

    CTest b(a);
    return 0;
}

 

이제 에러가 뜨지 않는다.