class CSample
{
private:
int num1;
int num2;
public:
void ShowSampleData()
{
cout << num1 << endl;
cout << num2 << endl;
}
public:
CSample(int n1, int n2)
: num1(n1)
, num2(n2)
{}
~CSample(){}
};
int main(void)
{
CSample s1(10, 20);
CSample s2 = s1; // ...ㄱ
s2.ShowSampleData();
return 0;
}
ㄱ 에서 s2객체가 생성된 다음에 s1과 s2 간의 맴버 대 맴버 복사가 일어난다.
또한 ㄱ 은 CSample s2(s1); 과 같다. 이 문장은 (1. CSample 형 객체를 생성. 2 객체의 이름을 s2 로 정한다. 3. s1을 인자로 받을 수 있는 생성자의 호출을 통해서 객체 생성을 완료.) 로 이루어진다.
ㄱ 과 같이 객체의 복사를 할 때 호출되는 생성자를 '복사 생성자' 라고 한다.
ㄱ을 풀이 하면 다음과 같다.
CSample(CSample& _copy)
: num1(_copy.num1)
, num2(_copy.num2)
{}
이때, 맴버 대맴버의 복사에 사용되는 원본을 변경시키는 것은 복사의 개념에 반하기 때문에 const 키워드를 사용해서 실수를 막는다. CSample( const CSample& _copy )
클래스에 생성자를 정의하지 않으면, 맴버 대 맴버의 복사를 진행하는 '디폴트 복사 생성자'가 장동으로 삽입된다.
* 묵시적 변환 막는 법
CSample s2 = s1 는 CSample s2(s1) 형태로 묵시적 변환이 일어나 복사 생성자가 호출된다.
하지만
explicit CSample(const CSample& _copy)
: num1(_copy.num1)
, num2(_copy.num2)
{}
이렇게 explicit 을 사용한다면 묵시적 변환이 발생하지 않아서 대입 연산자를 이용한 객체의 생성 및 초기화가 불가능하다.
* 얕은 복사
디폴트 복사 생성자는 맴버 대 맴버의 복사를 진행하는데 이러한 방식의 복사를 얕은 복사라 한다. 얕은 복사의 경우 맴버 변수가 힙 메모리 공간을 참조하는 경우에 문제가 된다.
class CCharactor
{
private:
char* nickname;
int level;
public:
CCharactor(char* _Myname, int _Mylevel)
{
int len = strlen(_Myname)+1;
nickname = new char[len];
strcpy(nickname, _Myname);
level = _Mylevel;
}
void ShowCharactorInfo() const
{
cout << nickname << endl;
cout << level << endl;
}
~CCharactor()
{
delete[]nickname;
cout << "delete completed" << endl
}
};
int main(void)
{
CCharactor C1("Shin", 10);
CCharactor C2 = C1; // ...ㄱ
C1.ShowCharactorInfo();
C2.ShowCharactorInfo();
return 0;
}
위의 코드는 다음의 결과를 보인다.
Shin
10
Shin
10
delete completed
이때 delete completed 는 왜 한번만 출력된 것일까
ㄱ 에서 C2 가 생성되면 디폴트 복사 생성자가 호출된다.
디폴트 복사 생성자는 얕은 복사로 맴버 대 맴버를 단순히 복사만 하므로 C1, C2 의 각 nickname 맴버 변수는 하나의 문자열에 대해서 포인터 참조를 하고 있다.
따라서 한 객체의 소멸자가 호출 된 시점에 그 문자열이 delete 로 삭제 되었기 때문에 남은 객체의 소멸자가 이미 존재하지 않는 주소에 대해 delete를 시도하는 것이 된다.
이 때문에 " delete completed " 는 한번만 출력된 것이다.
* 깊은 복사
위와 같은 문제를 해결하기 위해서는 문자열 또한 복사가 이루어져 C1, C2 의 각 맴버 변수 nickname 이 각기 다른 문자열 주소를 포인터 참조하면 된다.
이와 같은 복사의 형태를 깊은 복사라고 한다.
CCharactor(const CCharactor& _copy)
: nickname(nullptr)
, level(_copy.level)
{
nickname = new char[strlen(_copy.nickname) + 1];
strcpy(nickname, _copy.nickname);
}
- 맴버 변수 level 의 맴버 대 맴버 복사
- 메모리 공간 할당 후 문자열 복사
* 복사 생성자의 호출 시점
1. 기존에 생성된 객체를 이용해서 새로운 객체를 초기화 하는 경우 (위와 같은 경우)
2. call by value (값에 의한 호출)방식의 함수 호출 과정에서 객체를 인자로 전달하는 경우
3. 객체를 반환하되, 참조형으로 반환하지 않는 경우
공통적으로는. 객체를 새로 생성하며 생성과 동시에 동일한 자료형의 객체로 초기화 해야 한다는 것.
1번 : CCharactor C2 = C1;
C2 라는 메모리 공간의 할당과 동시에 C1에 저장된 값으로 초기화
2번 : CCharactor C1("Shin",10);
C1 객체의 생성자 함수의 매개변수에 대해 할당이 이루어짐과 동시에 초기화
3번 : char* GetCharactorNickname(){ return nickname; }
return 시에 별도의 메모리 공간에 할당되면서 동시에 이 공간에 반환값으로 초기화
<예시>
class Monster
{
private:
int level;
public:
Monster(int _level)
: level(_level)
{}
Monster(const Monster& _copy)
: level(_copy.level)
{
cout << "monster" << endl;
}
Monster& Levelup(int n)
{
level += n;
return *this;
}
void ShowLevel()
{
cout << level << endl;
}
};
Monster sampleMonster(Monster _sample)
{
cout << "Monster Sample" << endl;
return _sample;
}
int main(void)
{
Monster M1(10);
sampleMonster(M1).Levelup(5).ShowLevel();
M1.ShowLevel();
return 0;
}
위의 결과 는 다음과 같다.
monster
Monster Sample
monster
15
10
참조