A obj1 = obj2; -> 생성과 동시에 초기화
A obj1(obj2);
void fct(A ob){ } -> fct(obj1) : 단순 참조가 아닌, ob 객체를 새로 만든 뒤 obj1의 값으로 초기화
-> 인자가 전달 될 때 생성과 동시에 초기화된다.
-> ob객체 생성
->ob의 복사 생성자 호출->obj1값을 받아와서 초기화
A fct2(void){
A ob;
return ob;
}
//1. ob라는 객체가 복사 되어 메모리 공간 할당
//2. ob 객체를 인자로 받을 수 있는 생성자를 호출(복사 생성자)
// == 생성과 동시에 초기화
int fct3(void){
int num = 3;
return num ;
}
cout<<fct3();
//3은 메모리 공간에 복사된다.
복사 생성자가 호출되는 시점
- case 1 : 기존에 생성된 객체를 이용해서 새로운 객체를 초기화하는 경우
- case 2 : Call-by-value 방식의 함수 호출 과정에서 객체를 인자로 전달하는 경우
- case 3 : 객체를 반환하되, 참조형으로 반환하지 않는 경우
이들은 모두 다음의 공통점을 지닌다.
객체를 새로 생성해야 한다. 단, 생성과 동시에 동일한 자료형의 객체로 초기화해야 한다
복사 생성자의 호출 횟수는 프로그램의 성능과도 관계가 있기 때문에, 호출의 시기를 이해하는것이 매우 중요하다.
메모리 공간의 할당과 초기화가 동시에 일어나는 상황
메모리 공간이 할당과 동시에 초기화되는 상황의 대표적인 예시는 다음과 같다.
int num1 = num2;
int SimpleFunc(int n)
{
...
}
int main(void)
{
int num = 10;
SimpleFunc(num);//호출되는 순각 매개변수 n이 할당과 동시에 초기화
...
}
int SimpleFunc(int n){
....
return n;//반환하는 순간 메모리 공간이 할당되면서 동시에 초기화
}
int main(void){
int num = 10;
cout<<SimpleFunc(num)<<endl;
....
}
위 예제들은 모두 할당과 동시에 초기화가 이뤄지는 경우이다.
결론 :
함수가 값을 반환하면, 별도의 메모리 공간이 할당되고, 이 공간에 반환 값이 저장된다(반환 값으로 초기화된다.)
이러한 상황은 객체를 대상으로 해도 달라지지 않는다.
case 1
Person man1("Lee dong woo", 29);
Person man2 = man1; //복사 생성자 호출
case 2 & case 3
SoSimple SimpleFuncObj(SoSimple ob){
....
return ob;//반환하는 순간 메모리 공간이 할당되면서 동시에 초기화
}
int main(void){
SoSimple obj;
SimpleFuncObj(obj);//SimpleFuncObj함수가 호출되는 순간, 매개변수 ob 객체가 생성되고(메모리 공간이 할당)
//인자로 전달된 obj객체로 초기화된다.
....
}
int SimpleFunc(int n){//인자 전달 시 선언과 동시에 초기화
...
return n;
}
//반환 시 메모리 공간 할당과 동시에 초기화
int main(void){
int num = 10;
cout<<SimpleFunc(num)<<endl;
....
}
복사 생성자의 호출 case의 확인 1
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);
cout<<"함수호출 후" <<endl;
return 0;
}
실행 결과를 통해서 함수에 인자를 전달하는 과정에서 복사 생성자가 호출됨을 확인할 수 있다.
초기화의 대상은 obj객체가 아닌, ob객체이다. 그리고 ob객체는 obj객체로 초기화 된다.
따라서 ob객체의 복사 생성자가 호출되면서, obj객체가 인자로 전달되어야 한다.
복사 생성자의 호출 case의 확인 2
#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;//ob객체를 반환. 반환형이 참조형이 아니므로 ob객체의 복사본이 만들어지면서
//반환이 진행된다.
}
int main(void) {
SoSimple obj(7);
SimpleFuncObj(obj).AddNum(30).ShowData();
//SimpleFunObj함수가 반환한 객체를 대상으로 AddNum 함수를 호출하고,
//AddNum 함수가 반환하는 참조값을 대상으로 ShowData함수를 호출한다.
obj.ShowData();
return 0;
}
객체를 반환하게 되면 '임시 객체'라는 것이 생성되고, 이 객체의 복사 생성자가 호출되면서 return문에 명시된 객체가 인자로 전달된다.
즉, 최종적으로 반환되는 객체는 새롭게 생성된 임시객체이다.
따라서 함수 호출이 완료되고 나면, 지역적으로 선언된 객체 ob는 소멸되고 obj객체와 임시 객체만 남는다.
반환할 때 만들어진 객체의 소멸 시점
#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();
cout << "*********** after make!" << endl << endl;
const Temporary& ref = Temporary(300);//참조값이 반환되므로 참조자로 참조 가능!
cout << "*********** after make!" << endl << endl;
return 0;
}
Temporary(200).ShowTempInfo(); -> (임시객체의 참조 값).ShowTempInfo();
임시 객체가 생성된 위치에는 임시 객체의 참조 값이 반환된다.
const Temporary &ref = Temporary(300);
임시 객체 생성 시 반환되는 참조 값이 참조자 ref에 전달되어, ref가 임시 객체를 참조하게 된다.
SimpleFuncObj(obj).AddNum(30);
즉, 반환을 위해서 임시객체가 생성은 되지만, 이 객체는 메모리 공간에 존재하고,
이 객체의 참조 값이 반환되어서 AddNum 함수의 호출이 진행된 것이다.
결론 :
- 임시 객체는 다음 행으로 넘어가면 바로 소멸된다.
- 참조자에 참조되는 임시객체는 바로 소멸되지 않는다.
#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;
}
SoSimple tempRef = SimpleFuncObj(obj);
이 문장에서는 tempRef라는 새로운 객체를 생성해서, 반환되는 객체를 가지고 대입 연산을 진행하는 것 처럼 보이지만,
추가로 객체를 생성하지 않고, 반환되는 임시객체에 tempRef라는 이름을 할당하고 있음을 보여준다.
(객체의 생성 수를 하나 줄여서 효율성을 높인다.)
'공부 > C++' 카테고리의 다른 글
[C++] 6-2. 클래스와 함수에 대한 friend 선언 (0) | 2024.10.31 |
---|---|
[C++] 6-1. const 보충 (0) | 2024.10.31 |
[C++] 5-2. 깊은 복사와 얕은 복사 (0) | 2024.10.31 |
[C++] 5-1. 복사 생성자 (0) | 2024.10.30 |
[C++] 4-4. 클래스와 배열 그리고 this 포인터 (0) | 2024.09.30 |