본문 바로가기
C++

[C++]복사 생성자와 delete 키워드

by Junk_Seo 2018. 1. 24.
반응형

복사 생성자

C++ 클래스에는 프로그래머가 직접 구현하지 않아도 생성되는 생성자들이 있는데 그중 하나가 바로 복사 생성자입니다.

이름과 동일하게 복사 생성자는 class 변수에 다른 변수의 값을 복사하는 데 사용되는 생성자입니다. 

생성자이기 때문에 변수의 생성과 동시에 초기화되어야 합니다.

 

default 복사 생성자의 모습은 다음과 같습니다.

 

<CLASS_NAME>(const <CLASS_NAME>& cData);

 

생성자이기 때문에 반환형은 없으며 인자로 const 레퍼런스로 받습니다.

class C_DATA {
private:
	int *m_pData;
	int m_nSize;
public:
	C_DATA();
	C_DATA(const C_DATA & cInput);
	void init(int nSize);
};

int main()
{
	C_DATA cData1;
	cData1.init(10);

	C_DATA cDate2(cData1);
    return 0;
}

C_DATA::C_DATA() :
	m_pData(nullptr),
	m_nSize(0) {
}

C_DATA::C_DATA(const C_DATA & cInput)
{
	...
}

void C_DATA::init(int nSize) {
	m_pData = new int[nSize]{};
	m_nSize = nSize;
}

 

default 복사 생성자를 통해 발생하는 문제 때문에 생겨난 얕은 복사와 깊은 보가에 대한 내용이 있는데, 여기서는 그러한 내용을 다루지는 않겠습니다. 이에 대한 내용은 따로 검색하시면 나오기 때문입니다. 

 

여기서는 복사 생성자를 막는 방법에 대해서만 다루도록 하겠습니다.

 

복사 생성자를 사용하지 못하게 막는 방법에는 2가지 방법이 있습니다.

1. 복사 생성자를 private로 선언

복사 생성자를 private로 선언하게 되면 class 외부에서는 이 복사생성자를 호출할 수 없기 때문에 컴파일 과정에서 error를 발생시켜 줍니다.

class C_DATA {	
private:
	C_DATA(const C_DATA & cInput);
public:
	C_DATA();
};

이렇게 private로 복사생성자를 선언하고 구현하지 않는다면 class 외부에서는 복사생성자를 사용할 수 없게 됩니다.

2. delete 키워드를 사용하여 삭제하기

다음 방법은 delete 키워드를 사용하여 함수를 명시적으로 삭제하는 방법입니다.

class C_DATA {	
public:
	C_DATA();
	C_DATA(const C_DATA & cInput) = delete;
};

이렇게 delete 키워드를 사용하여 복사생성자를 삭제하고 삭제된 함수를 정의하거나 호출하게 되면 컴파일 과정에서 error를 발생시키게 됩니다.

 

위의 두 가지 방식 중 두 번째 방식을 주로 사용합니다.

출처 : https://msdn.microsoft.com/ko-kr/library/dn457344.aspx

 

복사 생성자가 호출되는 시점(2018.01.25 추가)

1. 기존에 생성된 객체를 이용해서 새로운 객체를 초기화하는 경우

C_DATA C1;
C_DATA.init();
C_DATA.setData(100);
C_DATA C2(C1);

 

파란색 글씨에서 복사생성자의 호출이 이루어진다.

init과 set이 끝난 C1 class 객체를 C2 class 객체에서 복사생성자를 호출하여 복사하고 있는 형태이다.

2. 함수의 인자로 call by value 형태로 인자를 받는 경우

void func(C_DATA cData);

int main()
{
	C_DATA cData;
	cData.setData(100);
	func(cData);
    return 0;
}

void func(C_DATA cData)
{
	printf("%d \n", cData.getData());
}

C/C++은 call by value 기반의 언어이기 때문에 인자를 레퍼런스가 아닌 형태로 받을 경우 무조건 임시 변수를 생성하여 메모리를 잡고 그 임시 변수에 인자로 받은 값을 call by value 형태로 복사한 뒤 사용한다.

따라서 함수의 인자로 객체를 받을 경우 위 코드의 파란색 부분에서 C_DATA class의 임시 객체를 생성하고 바로 인자를 복사생성자에 의해서 값을 복사하게 된다.

3. 함수의 반환 형태가 call by value 형태로 반환하는 경우

C_DATA func();

int main()
{
	func();
    return 0;
}

C_DATA func()
{
	C_DATA c1;
	return c1;
}

2번째 내용과 비슷하게 반환 형식을 레퍼런스가 아닌 형태로 반환할 경우 임시 변수를 생성해 메모리를 잡고 거기에 반환할 값을 복사한 뒤 그 임시 변수를 반환하게 된다.

따라서 위의 코드에서 파란색 부분처럼 객체를 반환하게 될 경우 반환 과정에서 C_DATA라는 임시 객체를 생성하고 반환하는 c1 객체를 복사생성자를 통해 복사한 뒤 반환하게 됩니다.

 

복사 생성자의 호출을 막는 이유(2018.01.25 수정)

프로그래머가 class를 구현하는 경우 default 생성자만 구현하여 단순히 멤버 변수를 초기화하는 기능만 추가합니다. 따로 동적할당과 같은 기능이 필요하다면 init 함수를 따로 구현하여 사용자가 그 함수를 호출하게 하는 것이 좋습니다.

class 가 모듈을 call by value의 형태로(얕은 복사와 깊은 복사 모두) 복사를 하게 되면 프로그래머가 의도와 다르게 사용이 가능하게 되고 또 많은 리소스를 사용하게 되어 비효율적입니다. 또 중요한 것은 class라는 모듈에 무엇이 들어있는지도 모르면서 복사를 하는 것은 굉장히 좋지 않습니다. 

따라서 프로그래머는 본인이든 사용자든 이러한 복사 기능을 사용하지 못하게 막아야 합니다. 그리고 필요한 값이 있다면 get으로 시작하는 함수를 구현하여 값을 사용하는 것이 바람직합니다.

 

 

 

****

복사 생성자는 생성자의 원리와 레퍼런스의 개념을 이해하기 위해서 숙지하고 있어야 하는 내용이지만 실제 프로그램을 구현할 때에는 복사 생성자의 사용을 막아야 합니다.

반응형