1. 복사 생성자
복사 생성자(Copy Constructor)
는 객체를 복사할 때 호출되는 생성자- class에 정의하지 않을 경우 컴파일러가 자동으로 default 복사 생성자를 생성
// 복사 생성자 예제 코드
#include <iostream>
using namespace std;
class CMyData
{
private:
int m_Data = 0;
public:
CMyData()
{
cout << "MyData()" << endl;
}
// 복사 생성자
CMyData(const CMyData &rhs)
{
this->m_Data = rhs.m_Data;
cout << "CMyData(const CMyData &)" << endl;
}
int GetData() const
{
return m_Data;
}
void SetData(int nParam)
{
m_Data = nParam;
}
};
int main()
{
CMyData a;
a.SetData(10);
CMyData b(a); // 복사 생성자 호출
cout << b.GetData() << endl;
return 0;
}
2. 함수 호출과 복사 생성자
- 복사 생성자가 호출되는 경우
- 객체를 복사하는 경우
- 함수의 매개변수로 객체를 전달하는 경우
- 함수의 리턴타입이 class인 경우(임시객체 생성)
- 매개변수로 객체를 전달할 경우에는
참조자(reference)
를 사용(성능 문제) - 상세한 내용은 다음 예제코드에 주석으로 설명하였음
// 함수의 매개변수로 객체가 전달되는 경우의 예제코드
#include <iostream>
using namespace std;
class CTestData
{
private:
int m_nData = 0;
public:
CTestData(int nParam) : m_nData(nParam)
{
cout << "CTestData(int)" << endl;
}
// 복사 생성자
CTestData(const CTestData &rhs) : m_nData(rhs.m_nData)
{
cout << "CTestData(const CTestData &)" << endl;
}
// 읽기 전용 상수형 함수
int GetData() const {
return m_nData;
}
// 멤버 변수를 수정하는 함수
void SetData(int nParam){
m_nData = nParam;
}
};
// 매개변수가 CTestData 클래스 타입이므로 복사 생성자 호출
// 해당 함수 호출 시, 객체의 복사본이 생성 (원본 객체에 대한 조작 X)
// * 해결방법: 참조자(Reference) 이용!
// void TestFunc(CTestData ¶m);
// 객체를 참조형태로 매개변수로 전달할 경우 복사본 객체를 생성하는 것이 아니라 원본을 이용하게 됨!
void TestFunc(CTestData param)
{
cout << "TestFunc()" << endl;
// 피호출자 함수에서 매개변수 인스턴스의 값 변경
param.SetData(20);
}
int main()
{
cout << "***Begin***" << endl;
// a 객체 생성 및 생성자를 이용하여 멤버 변수를 10으로 초기화
CTestData a(10);
// 생성된 객체를 TestFunc()함수의 매개변수로 전달 --> 복사 생성자 호출
// TestFunc()에서 SetData()함수를 호출하여 객체의 값을 변경하려고 시도하지만 결과적으로 변경 되지 않음
// 그 이유는 임시객체의 멤버 값을 변경 시킬 뿐 실제 객체 a의 멤버 값을 변경시키는 것은 아니기 때문
// TestFunc()함수 종료와 함께 임시객체는 사라짐
TestFunc(a);
// 함수 호출 후 a의 값을 출력한다.
cout << "a: " << a.GetData() <<endl;
cout << "***End***" << endl;
return 0;
}
// 실행결과
//
// ***Begin***
// CTestData(int)
// CTestData(const CTestData &)
// TestFunc()
// a: 10
// ***End***
3. 깊은 복사(Deep Copy) & 얕은 복사(Shallow Copy)
깊은 복사: 복사에 의해 값이 실제로 각각 생성되는 것
얕은 복사: 각각의 값이 생성되는 것이 아니라 포인터에 의해 접근 방법만 복사된 것
(포인터에 의해 주소값이 복사된 경우)- 얕은 복사의 경우 다양한
메모리 문제
가 발생할 수 있음(ex.이미 해제한 메모리에 대해 중복 해제 하는 경우) - 아래의 예제코드에서
pB = pA
부분을 통해 각각 동적할당된 메모리를 가리키는 것이 아니라 결국 pA와 pB가 동일한 메모리 주소를 가리킴(Shallow Copy) - 깊은 복사가 되게 하려면
*pB = *pA
로 수정
#include <iostream>
using namespace std;
int main()
{
int *pA, *pB;
pA = new int;
*pA = 10;
pB = new int;
pB = pA; // shallow copy
// *pB = *pA; <-- deep copy
cout << *pA << endl;
cout << *pB << endl;
return 0;
}
- 클래스에서 멤버 변수로 포인터 변수를 갖는 경우 얕은 복사에 의한 메모리 문제가 발생할 수 있음
- 다음의 예제코드는 소멸자가 호출될 때 메모리 에러가 발생
#include <iostream>
using namespace std;
class CMyData
{
private:
// 포인터 타입의 멤버변수
int *m_pnData = nullptr;
public:
CMyData(int param)
{
m_pnData = new int;
*m_pnData = param;
}
int GetData() const
{
if(m_pnData != NULL)
{
return *m_pnData;
}
}
// 소멸자
~CMyData()
{
delete m_pnData;
}
};
int main()
{
CMyData a(10);
CMyData b(a); // 복사 생성자 호출
cout << a.GetData() << endl;
cout << b.GetData() << endl;
return 0;
}
- 클래스 내부에 복사 생성자가 정의 되어 있지 않기 때문에
CMyData b(a);
에서 컴파일러에 의해 자동으로 default 복사 생성자 호출(default 복사 생성자는 얕은 복사 수행
) - 객체 a, b는 동일한 메모리 m_pnData를 가리킴
- 객체 a의 소멸자가 호출되면 할당 받은 메모리가 해제되고, 이어서 객체 b의 소멸자가 호출되면서 메모리 에러가 발생함
- 이미 a객체의 소멸자에 의해 가리키던 주소의 메모리가 해제 되었기 때문
- 메모리 중복 해제 문제를 해결하기 위해서는 깊은 복사를 수행하는 복사 생성자를 반드시 클래스 내부에 구현해야 함
// 클래스 내부에 다음과 같이 복사 생성자를 구현하여 깊은 복사를 수
// 깊은 복사 수행 복사 생성자 선언 및 정의
CMyData(const CMyData &rhs)
{
cout << "CMyData(const CMyData &)" << endl;
// 메모리 할당
m_pnData = new int;
// 포인터가 가리키는 위치에 값을 복사 (별도의 주소 위치에 값을 넣는 것으로 얕은 복사가 아니라 깊은 복사를 의미)
*m_pnData = *rhs.m_pnData;
}
- 대입 연산자(=)에 의해 객체를 복사하는 경우에도 얕은 복사가 이루어지기 때문에 메모리 문제가 발생할 수 있음
- 연산자 오버로딩(operator overloading)을 통해 문제 해결 가능(5장 연산자 오버로딩)
- 클래스 내부에
복사 생성자와 대입 연산자 오버로딩
구현을 통해 얕은 복사로 인해 발생하는 메모리 문제 해결!
// main()
int main()
{
Test a(10);
Test b(10);
a = b; // 대입 연산자에 의한 얕은 복사 수행
}
// 클래스 내부에 대입 연산자의 함수를 오버로딩(깊은 복사)
CMydata& operator=(const CMyData &rhs)
{
*m_nData = *rhs.m_pnData;
}
본 포스팅 내용은
이것이 C++이다
를 학습한 내용을 바탕으로 요약한 글입니다. 코드에 문제가 있을 수 있으며 다소 부정확한 내용이 있을 수 있으니 참고하시기 바랍니다. 잘못된 내용이 있다면 덧글로 남겨주시면 내용을 반영하여 수정하도록 하겠습니다. 감사합니다.
[Reference]
[1] 이것이 C++이다, p.181-201