깊은 복사(Deep Copy)와 얕은 복사(Shallow Copy)

2024. 3. 20. 14:38프로그래밍/C++

깊은 복사는 복사에 의해 실제로 두 개의 값이 생성되는 것이다.

얕은 복사는 대상이 되는 값은 하나뿐인데 접근 포인터가 두 개로 느는 것이다.

 

얕은 복사의 문제점을 살펴보자

#include <iostream>
using namespace std;

int main()
{
	int* pA, * pB;

	pA = new int;
	*pA = 10;

	pB = new int;
	pB = pA;

	cout << *pA << endl;
	cout << *pB << endl;

	delete pA;
	delete pB;

	return 0;
}
main
pA
0xFFF000BD0
pointer to int
0x5C7BC80💀
pB
0xFFF000BD8
pointer to int
0x5C7BC80💀

Error Message : 💀는 데이터 경계에 할당되지 않았거나 잘못 정렬된 메모리를 가리키는 포인터를 의미합니다. 💀 위치는 근사하며 포인터의 실제 주소와 일치하지 않을 수 있습니다. 자세한 내용을 보려면 아래의 '바이트 수준 데이터 보기'를 선택하십시오:

pA = pB --> *pA = *pB 로 수

포인터가 가리키고 있는 메모리의 내용이 복사된다!

 

 

두번째 조심해야 할 점은 포인터가 '변수'일 때 문제가 되는 점입니다. 변하는 수를 가리키다 보니 가리키면 안 될 대상을 가리키는 논리적 오류가 발생할 수 있습니다.

 

포인터가 존재했을 때의 얕은 복사

#include <iostream>
using namespace std;

class CMyData
{
public:
	CMyData(int nParam) 
	{
		m_pnData = new int;
		*m_pnData = nParam;
	}

	int GetData()
	{
		if (m_pnData != NULL)
			return *m_pnData;

		return 0;
	}

private:
	int* m_pnData = nullptr;
};

int main()
{
	CMyData a(10);
	CMyData b(a);
	cout << a.GetData() << endl;
	cout << b.GetData() << endl;

	return 0;

}

위 코드에서 컴파일러는 아래의 얕은 복사를 자동으로 생성한다.

CMyData(const CMyData &rhs)
{
    m_pnData = rhs.m_pnData;
}

위 코드에서 소멸자를 추가할 경우 아래와 같습니다.

#include <iostream>
using namespace std;

class CMyData {
public:
    CMyData(int nParam) {
        m_pnData = new int;
        *m_pnData = nParam;
    }

    // 복사 생성자 추가
    CMyData(const CMyData& rhs) {
        m_pnData = new int;
        m_pnData = rhs.m_pnData; 
    }

    ~CMyData() { // 소멸자 추가
        if (m_pnData != nullptr) {
            delete m_pnData;
            m_pnData = nullptr;
        }
    }

    int GetData() {
        if (m_pnData != nullptr)
            return *m_pnData;

        return 0;
    }

private:
    int* m_pnData = nullptr;
};

int main() {
    CMyData a(10);
    CMyData b(a);
    cout << a.GetData() << endl;
    cout << b.GetData() << endl;

    return 0;
}

이 경우에는 한 객체가 소멸되면 해당 메모리를 가리키던 포인터가 무효화됩니다. 그러나 다른 객체가 여전히 그 메모리를 가리키고 있기 때문에 소멸자가 그 메모리를 해제하는 작업은 무효화된 포인터를 조작하게 됩니다. 이것은 메모리 오염(memory corruption)을 유발할 수 있습니다.

 

이러한 경우를 예방하기 위한 깊은 복사를 수행해야 합니다. 깊은 복사를 수행할 경우 아래와 같습니다.

#include <iostream>
using namespace std;

class CMyData {
public:
    CMyData(int nParam) {
        m_pnData = new int;
        *m_pnData = nParam;
    }

    // 복사 생성자를 깊은 복사로 수정
    CMyData(const CMyData& rhs) {
        m_pnData = new int;
        *m_pnData = *rhs.m_pnData; // 깊은 복사(deep copy)
    }

    ~CMyData() { // 소멸자 추가
        if (m_pnData != nullptr) {
            delete m_pnData;
            m_pnData = nullptr;
        }
    }

    int GetData() {
        if (m_pnData != nullptr)
            return *m_pnData;

        return 0;
    }

private:
    int* m_pnData = nullptr;
};

int main() {
    CMyData a(10);
    CMyData b(a);
    cout << a.GetData() << endl;
    cout << b.GetData() << endl;

    return 0;
}

 

'프로그래밍 > C++' 카테고리의 다른 글

Pass by value VS Pass by reference  (0) 2024.03.21
대입 연산자  (0) 2024.03.21
복사 생성자  (0) 2024.03.20
Dynamic vs Static  (0) 2024.03.19
Valid struct operation  (0) 2024.03.19