복사생성자 대입연산자
복사생성자
복사생성자는 객체와 객체가 복사될때 호출 되는 생성자이다. 복사생성자를 따로 선언해 주지 않으면 컴파일러가 자동적으로 복사생성자를 생성한다.
1
2
3
4
5
6
7
8
class TClass{
TClass(){
// 기본생성자
}
TClass(const TClass& cls){
// 복사생성자!
}
};
복사생성자는 기본 생성자에 const TClass& 을 인자로 갖는다.
복사를 수행할 클래스가 변경이 되지 않아야 하므로 const 형으로 선언하고
const TClass 로 선언할 경우 반복적으로 계속 복사생성자가 호출 되므로 refrence 형으로 선언하여야 한다.
얕은복사 & 깊은 복사
기본 default 복사생성자의 경우에는 얕은 복사를 수행한다.
- 얕은 복사(shallow copy) : 얕은 복사는 단순 값만 복사한다. 동적으로 할당된 메모리는 복사하지 못한다. 얕은 복사를 수행하는 객체가 원래 복사를 수행했던 기존 객체가 메모리에서 사라진다면 에러가 발생할수 있다.
- 깊은 복사(deep copy) : 깊은 복사는 동적으로 할당된 값까지 복사한다.
복사생성자가 호출 되는경우
- 기존에 생성된 객체를 이용해서 새로운 객체를 초기화 할경우
1
TClass cls = tCls;
- Call-by-value 방식으로의 호출(pointer 와 reference 형이 아닌) 과정에서 객체를 인자로 전달 할 경우
1
2
3
void func(TClass cls); // 복사생성자 호출
void funcRef(TClsss& cls); // 복사생성자 호출 안됨
void funcPointer(TClsss* pCls); // 복사생성자 호출 안됨
- 객체를 반환하는데 참조형으로 반환하지 않을 경우
1
2
3
4
5
TClass func(){
TClass cls;
//..code
return cls;
}
TroubleShooting
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class TClass{
public:
TClass(){
// 그냥 생성자!
printf("생성자 호출\n");
}
TClass(const TClass& cls){
// 복사생성자! - 멤버변수들의 복사를 수행해야 한다.
printf("복사생성자 호출\n");
}
void print(){
// 멤버함수
}
};
class PClass{
public:
TClass tCls;
PClass& getTClassRef(){
// 복사생성자 없이 그대로 cls 복사본을 전달해줌
return cls;
}
PClass getTClass(){
// 복사한 cls 값을 리턴해줌
return cls;
}
};
int main(void){
TClass cls; // 그냥 생성자만 호출된다.
TClass cls2 = cls; // 복사생성자 호출됨
PClass pCls;
pCls.tCls = cls;
TClass tCls1 = pCls.getTClassRef(); // 복사 생성자 호출됨
TClass& tCls2 = pCls.getTClassRef(); // 복사 생성자 호출되지 않는다. 값 그대로의 Tcalss 를 가져온다.
TClass tCls3 = pCls.getTClass(); // 복사생성자 호출됨
// tCls1 과 tCls3 는 복사생성자가 호출된 새로운 객체다
// cls 와 tCls1 과 tCls3와 주소 다르다.
// cls 는 tCls2 는 주소가 같다.
mCls.getTClassRef().print(); // 복사생성자가 호출되지 않는다.
mCls.getTClass().print(); // 복사생성자 호출된다.
// 위 두 객체의 결과는 다르게 나올수 있다.
// 복사생성자가 재대로 값을 복사를 못할경우 두 멤버 변수의 값은 다를 수 있다.
return 0;
}
대입연산자
대입 연산자는 연산자 오버로딩의 결과로써 실행된다. 복사 생성자와 비슷하게 값에 copy를 수행한다. 생성자와 마찬가지로 따로 선언하지 않을 경우 default로 얕은 복사를 하는 대입 연산자를 컴파일러가 자동으로 만든다.
1
2
3
4
5
6
7
8
9
10
class TClass{
public:
// 기본 대입연산자
TClass& operator =(const TClass& cls){
if(this != &cls){
// 복사수행
}
retur *this;
}
};
대입 연산자는 TClass& 로 reference 형을 return 한다. 이는 cls = cls2 = cls3; 처럼 연속으로 대입 연산자를 수행가능 하도록 만들기 위함이다. const TClass& cls 으로 선언한 이유는 복사 생성자 처럼 연속으로 복사 생성자가 호출 되지 않도록 하기 위함이다.
1
2
3
4
5
6
int main(void){
TClass cls; // 그냥 생성자만 호출된다.
TClass cls2 = cls; // 대입 연산자가 호출 되는게 아니라 복사 생성자가 호출됨
TClass cls3;
cls3 = cls2; // 대입 연산자가 호출됨
}
유의 할점!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TClass(){
public:
PClass *pCls; // 특정 객체를 가리키는 pointer
};
PClass* testFunction(){
TClass cls;
// code;
return cls.pCls;
}
int main(void){
TClass tCls;
PClass* pCls = tCls.testFunction();
// TClass 가 메모리에서 사라지면서 pCls 는 값을 잃어 버린다.
return 0;
}
PClass* testFunction(){
// 복사생성자를 호출해서 그 값을 리턴해주던지
// PClass& 형을 리턴하는게 좋다!
return &PClass(cls.pCls);
}