20. [C++] 복사 생성자의 호출시점

2024. 4. 15. 14:03[C++]/C++ 언어 기초

복사 생성자가 호출되는 시점

  • 이들의 공통점은 "객체를 새로 생성해야 한다. 단, 생성과 동시에 동일한 자료형의 객체로 초기화해야 한다"
  • 복사 생성자의 호출 횟수는 프로그램의 성능과도 관계있다.

메모리 공간의 할당과 초기화가 동시에 일어나는 상황

int num1=num2;
  • 이 예시에서는 num1이라는 이름의 메모리 공간을 할당과 동시에 num2에 저장된 값으로 초기화시킨다.
  • 즉, 할당과 동시에 초기화가 이뤄진다. 그리고 다음의 경우에서도 할당과 동시에 초기화가 이뤄진다.
int SimpleFunc(int n)
{
. . . . 
}
int main(void)
{
    int num=10;
    SimpleFunc(num);	
    //호출되는 순간 매개변수 n이 할당과 동시에 초기화!
    . . . .
}
  • 위의 코드에서 SimpleFunc 함수가 호출되는 순간에 매개변수 n이 할당과 동시에 변수 num에 저장되어 있는 값으로 초기화된다.
  • 이렇듯 매개변수도 함수가 호출되는 순간에 할당되므로, 이 상황도 메모리 공간의 할당과 초기화가 동시에 일어나는 상황이다.

 

int SimpleFunc(int n)
{
    return n;  //반환하는 순간 메모리 공간이 할당되면서 동시에 초기화
}

int main(void)
{
    int num = 10;
    cout<<SimpleFunc(num)<<endl;
    ....
}
  • 반환되는 값을 별도의 변수에 저장하는 것과 별개로, 값을 반환하면 반환된 값은 별도의 메모리 공간이 할당되서 저장된다.
  • cout<<SimpleFunc(num)<<endl; 문장에서 보이듯, 반환되는 값을 메모리 공간 어딘가에 저장해놓지 않았다면, cout에 대한 출력이 불가능하다.
  • 결론적으로 함수가 값을 반환하면, 별도의 메모리 공간이 할당되고, 이 공간에 반환 값이 저장된다(반환 값으로 초기화된다).

 

  • 이러한 상황은 객체를 대상으로 해도 똑같다.
  • 위의 코드에서 SimpleFuncObj 함수가 호출되는 순간, 매개변수로 선언된 ob객체가 생성되고(ob를 위한 메모리 공간이 할당되고), 이는 인자로 전달된 obj객체로 초기화된다. 즉, 메모리 공간이 할당되면서 동시에 초기화 된다.

할당 이후, 복사 생성자를 통한 초기화

  • 앞서 객체가 생성 및 초기화되는 경우에 대해 정리했는데, 그렇다면 이 때 초기화는 어떻게 이뤄지겠는가?
  • 일단 상황적으로 판단해보면, 초기화는 멤버 대 멤버가 복사되는 형태로 이뤄져야 한다.
  • 그래서 '복사 생성자의 호출' 방식으로 초기화를 진행한다. 

PassObjCopycon.cpp

#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);
	cout<<"함수호출 후"<<endl;
	return 0;
}
함수호출 전
Called SoSimple(const SoSimple& copy)
num: 7
함수호출 후
  • 우선 실행결과를 통해 함수에 인자를 전달하는 과정에서 복사 생성자가 호출됨은 확인됐다. 그리고 이로 인해 멤버변수 num에 저장된 값이 복사 됨을 확인했다.
  • 복수 생성자의 호출주체가 누군지 확인해보자.
  • 위 그림에서 보이듯이 초기화의 대상은 obj 객체가 아닌, ob 객체이다.
  • 그리고 ob 객체는 obj 객체로 초기화된다.
  • 따라서 ob 객체의 복사 생성자가 호출되면서, obj 객체가 인자로 전달되어야 한다.

 

복사생성자가 호출되는 세 번째 경우

ReturnObjCopycon.cpp

#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

 

  • 위 그림에서 보이듯이 객체를 반환하게 되면, '임시객체'라는 것이 생성되고, 이 객체의 복사 생성자가 호출되면서 return문에 명시된 객체가 인자로 전달된다.
  • 즉, 최종적으로 반환되는 객체는 새롭게 생성되는 임시객체이다.
  • 따라서 함수호출이 완료되고 나면, 지역적으로 선언된 객체 ob는 소멸되고 obj 객체의 임시객체만 남는다.

 

SimpleFuncObj(obj).AddNum(30).ShowData();

 

  • AddNum 함수의 호출로 인해, 임시객체에 저장된 값이 30 증가한다.
  • 이어서 ShowData 함수호출을 통해 임시객체에 저장된 값을 출력하고 있다.
  • 그리고 이때 출력된 값은, 위 코드의 다음 행과는 다르다. ShowData 함수의 호출대상인 두 객체가 서로 별개이기 때문에 이는 당연한 결과이다.

반환할 때 만들어진 객체는 언제 사라져요?

  • 임시객체도 임시변수와 마찬가지로 임시로 생성되었다가 소멸되는 객체이다. 그리고 임시객체는 임의로 만들 수도 있다.

IKnowTempObj.cpp

#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<<"********** end of main!"<<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
********** end of main!

destroy obj: 300

 

 

클래스 외부에서 객체의 맴버함수를 호출하기 위해 필요한 것은 다음 세 가지 중 하나이다.

  • 객체에 붙여진 이름
  • 객체의 참조 값(객체 참조에 사용되는 정보)
  • 객체의 주소 값
Temporary(100);
  • 위의 코드를 분석해보면,  100으로 초기화된 Temporary임시객체 생성
Temporary(200).ShowTempInfo();
  • 이는 임시객체여서 다음 행으로 넘어가면 소멸된다.
  • 임시객체가 생성된 위치에는 임시객체의 참조 값이 반환된다. 위 문장의 경우 먼저 임시객체가 생성되면서 다음의 형태가 된다.
(임시객체의 참조 값).ShowTempInfo();
  • 그래서 이어서 멤버함수의 호출이 가능한 것이다. 또한 이렇듯 '참조 값'이 반환되기 때문에 다음과 같은 문장의 구성도 가능한 것이다.
const Temporary &ref=Temporary(300);

 

  • 임시객체 생성시 반환되는 '참조 값'이 참조자 ref에 전달되어, ref가 임시객체를 참조하게 된다.
  • 참조자에 참조되는 임시객체는 바로 소멸되지 않는다.

 

결론을 내리자면,

  • 임시객체는 다음 행으로 넘어가면 바로 소멸되어 버린다.
  • 참조자에 참조되는 임시객체는 바로 소멸되지 않는다.
반응형