일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 오블완
- Classification
- 회귀
- OpenAI
- AI
- gpt
- regression
- 지도학습
- 해커톤
- LLM
- ChatGPT
- LG Aimers
- supervised learning
- LG
- PCA
- 딥러닝
- GPT-4
- 머신러닝
- Machine Learning
- LG Aimers 4th
- deep learning
- 티스토리챌린지
- 분류
- Today
- Total
SYDev
C++ Chapter 05-3 : 복사 생성자의 호출 시점 본문
복사 생성자의 호출 시점
앞선 게시글로 다음의 경우에 복사 생성자가 호출된다는 사실은 알게 되었다.
Person man1("Lee dong woo", 29);
Person man2 = man1; // 복사 생성자 호출
이 경우를 포함해서 복사 생성자가 호출되는 경우는 크게 3가지가 있다.
- case 1 : 기존에 생성된 객체를 이용해 새로운 객체를 초기화(위의 경우)
- case 2 : Call-by-value 방식의 함수 호출 과정에서 객체를 인자로 전달하는 경우
- case 3 : 객체를 반환하되, 참조형으로 반환하지 않는 경우
+ case 2 -> 함수가 호출될 때 매개변수에 메모리 공간이 할당되고 해당 공간에 전달된 인자가 저장(변수의 초기화)됨
+ case 3 -> 함수가 값을 반환하면, 별도의 메모리 공간이 할당되고 해당 공간에 반환값이 저장(반환값으로 초기화)됨
이들은 모두 객체를 새로 생성해야 하고, 생성과 동시에 동일한 자료형의 객체로 초기화해야 한다는 공통점이 있다. 따라서 해당 상황에 해당하는 경우가 복사 생성자를 호출하는 시점이라고 생각할 수 있다.
사실 위의 경우에서 객체를 일반자료형으로 놓고 봤을 때, 모두 메모리 공간의 할당과 초기화가 동시에 일어나는 상황이다. 하지만, 해당 상황에서 일반 자료형을 객체로 다시 바꾸어 봐도 메모리 공간의 할당과 초기화가 동시에 일어난다는 사실은 변하지 않기 때문에 그대로 이해하면 될 거 같다.
이제 아래 예제들을 살펴보며 복사 생성자의 호출 시점에 대해서 이해해보자.
#include <iostream>
using namespace std;
class SoSimple
{
private:
int num;
public:
SoSimple(int n) : num(n)
{ }
SoSimple(const SoSimple& copy) : num(copy.num)
{
cout<<"Called SoSimple(const SoSimple& copy)"<<endl;
}
void ShowData()
{
cout<<"num : "<<num<<endl;
}
};
void SimpleFuncObj(SoSimple ob)
{
ob.ShowData();
}
int main(void)
{
SoSimple obj(7);
cout<<"함수호출 전"<<endl;
SimpleFuncObj(obj); // SimpleFuncObj 함수 내부에서 매개변수 ob의 멤버가 객체 obj의 멤버들과 같이 복사되면서 복사 생성자 호출
cout<<"함수호출 후"<<endl;
return 0;
}
함수호출 전
Called SoSimple(const SoSimple& copy)
num : 7
함수호출 후
위 예제에서는 Case 2의 경우를 보이고 있다. 또한, SimpleFuncObj 함수 내부의 매개변수인 ob 객체에 대해서 복사 생성자가 호출되었다는 것을 알 수 있다.
그렇다면 이번에는 Case 3의 예제를 살펴보자.
#include <iostream>
using namespace std;
class SoSimple
{
private:
int num;
public:
SoSimple(int n) : num(n)
{ }
SoSimple(const SoSimple& copy) : num(copy.num)
{
cout<<"Called SoSimple(const SoSimple& copy)"<<endl;
}
SoSimple& AddNum(int n)
{
num += n;
return *this;
}
void ShowData()
{
cout<<"num : "<<num<<endl;
}
};
SoSimple SimpleFuncObj(SoSimple ob)
{
cout<<"return 이전"<<endl;
return ob;
}
int main(void)
{
SoSimple obj(7);
SimpleFuncObj(obj).AddNum(30).ShowData();
obj.ShowData();
return 0;
}
Called SoSimple(const SoSimple& copy)
return 이전
Called SoSimple(const SoSimple& copy)
num : 37
num : 7
위 예제에서 함수 SimpleFuncObj의 내부에서는 앞서 보았듯이 매개변수 전달 과정에서 복사 생성자가 호출되었다. 그리고 이후에 return값이 반환되면서도 복사 생성자가 호출되었다는 것을 알 수 있다.
해당 경우에는 함수에서 객체를 반환하면서 '임시객체'라는 것이 생성되고 이 객체의 복사 생성자가 호출된다. 이어서 return문에 명시된 객체가 인자로 전달되고 (지역적으로 선언된 객체 ob는 소멸됨) 해당 임시객체에 저장된다. 이후에 해당 객체에 대해서 함수 AddNum, ShowData가 실행된 결과를 보면 임시객체에 저장된 값을 이용한다는 것을 알 수 있다.
임시객체의 소멸 시점
임시객체는 우리가 임의로 만들 수도 있다. 이와 관련된 다음 예제를 살펴보자.
#include <iostream>
using namespace std;
class Temporary
{
private:
int num;
public:
Temporary(int n) : num(n)
{
cout<<"create obj : "<<num<<endl;
}
~Temporary()
{
cout<<"destroy obj: "<<num<<endl;
}
void ShowTempInfo()
{
cout<<"My num is "<<num<<endl;
}
};
int main(void)
{
Temporary(100); // 임시객체 생성 방법
cout<<"********** after make!"<<endl<<endl;
Temporary(200).ShowTempInfo(); // (임시객체의 참조 값).ShowTempInfo();
cout<<"********** after make!"<<endl<<endl;
const Temporary &ref = Temporary(300); // 참조자 ref로 임시객체를 참조
cout<<"********** after make!"<<endl<<endl;
return 0;
}
create obj : 100
destroy obj: 100
********** after make!
create obj : 200
My num is 200
destroy obj: 200
********** after make!
create obj : 300
********** after make!
destroy obj: 300
클래스 외부에서 객체의 멤버함수를 호출하기 위해 필요한 것은 다음 세 가지 중 하나이다.
- 객체에 붙여진 이름
- 객체의 참조 값(객체 참조에 사용되는 정보)
- 객체의 주소 값
위 함수에서 Temporary(200).ShowTempInfo();와 같은 문장이 클래스의 멤버함수를 호출할 수 있는 이유도 임시객체가 생성되면서 임시객체가 자신의 참조 값을 반환하기 때문이다((임시객체의 참조 값).ShowTempInfo();).
또한 임시객체가 참조 값을 반환하기 때문에 다음과 같이 const Temporary &ref = Temporary(300); 참조자 ref가 해당 임시객체를 참조할 수 있게 되는 것이다.
본론으로 돌아와서 위 예제의 실행결과를 보면 참조자 ref가 임시객체를 참조하는 경우를 제외하고는 모두 해당 문장을 넘어가면 소멸되는 것을 볼 수 있다. 이 결과를 통해 우리는 임시객체의 다음 특징을 알 수 있다.
- 임시객체는 다음 행으로 넘어가면 바로 소멸된다.
- 참조자에 참조된 임시객체는 바로 소멸되지 않는다.
이제 다음으로 반환할 때 생성되는 임시객체의 소멸 시기를 확인하기 위해 예제를 살펴보자.
#include <iostream>
using namespace std;
class SoSimple
{
private:
int num;
public:
SoSimple(int n) : num(n)
{
cout<<"New Object : "<<this<<endl;
}
SoSimple(const SoSimple& copy) : num(copy.num)
{
cout<<"New Copy obj : "<<this<<endl;
}
~SoSimple()
{
cout<<"Destroy obj: "<<this<<endl;
}
};
SoSimple SimpleFuncObj(SoSimple ob)
{
cout<<"Parm ADR : "<<&ob<<endl;
return ob;
}
int main(void)
{
SoSimple obj(7);
SimpleFuncObj(obj);
cout<<endl;
SoSimple tempRef = SimpleFuncObj(obj);
cout<<"Return Obj : "<<&tempRef<<endl;
return 0;
}
New Object : 0x61fe00 .....31행의 obj 생성
New Copy obj : 0x61fe08 .....32행의 함수 호출로 23행의 매개변수 ob 생성
Parm ADR : 0x61fe08 .....25행의 실행
New Copy obj : 0x61fe04 .....26행의 return으로 인해 임시객체 생성
Destroy obj: 0x61fe04 .....26행 반환으로 생성된 임시객체 소멸
Destroy obj: 0x61fe08 .....매개변수 ob 소멸
New Copy obj : 0x61fe0c .....35행의 함수 호출로 23행의 매개변수 ob 생성
Parm ADR : 0x61fe0c .....25행의 실행
New Copy obj : 0x61fdfc .....26행의 return으로 인해 임시객체 생성
Destroy obj: 0x61fe0c .....매개변수 ob 소멸
Return Obj : 0x61fdfc .....참조자 tempRef가 참조하는 주소 값이 return으로 생긴 임시객체 주소 값과 동일
Destroy obj: 0x61fdfc .....tempRef가 참조하는 임시객체 소멸
Destroy obj: 0x61fe00 .....31행의 obj 소멸
지금까지 배운 내용을 종합해서 해당 실행 결과를 스스로 이해해보도록 하자.
+ 추가적으로 35행의 문장 SoSimple tempRef = SimpleFuncObj(obj);에서 새로운 객체를 생성해서 대입연산을 진행하는 것처럼 보이지만, 객체를 생성하지 않고 생성된 임시객체에 tempRef라는 이름을 할당함을 기억하자.
출처 : 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12
'Programming Lang > C++' 카테고리의 다른 글
C++ Chapter 06-2 : 클래스와 함수에 대한 friend 선언 (0) | 2023.07.17 |
---|---|
C++ Chapter 06-1 : const개념 보충 (0) | 2023.07.17 |
C++ Chapter 05-2 : '깊은 복사'와 '얕은 복사' (0) | 2023.07.17 |
C++ Chapter 05-1 : 복사 생성자(Copy Constructor) (1) | 2023.07.16 |
C++ Chapter 04-4 : 클래스와 배열 그리고 this 포인터 (0) | 2023.07.16 |