I'm FanJae.

[C++] this, Reference return 본문

C++/Basic

[C++] this, Reference return

FanJae 2024. 8. 18. 20:48

※ 본 포스트는 코드누리 C++ Basic 강의 내용을 보고 정리한 포스트입니다.

 

1. 객체를 어떻게 구분하는가?

#include <iostream>

class Point
{
	int x{0};
	int y{0};
public:
	void set(int a, int b)
	{
		x = a;
		y = b;	
	}
};

int main()
{
	Point pt1;
	Point pt2;

	pt1.set(10, 20);
	pt2.set(10, 20);
}

- 위와 같은 예제가 있다고 생각할때, 우리가 이와 같은 의문을 가질 수 있다.

- 멤버 데이터는 분명히 객체당 한 개씩 생성되고, 멤버 함수 또한 코드 메모리에 한 개만 만들어진다.

- 이는 객체가 여러 개 생성되도 그러하다. 대체 어떤 객체인지 어떻게 구분하는 것일까?

void set(int a, int b)
{
	x = a;
	y = b;	
}
pt1.set(10,20);
pt2.set(10,20);

- pt1,pt2에서 set() 호출할때 이들의 정보를 set에 준 적이 없다.

- 이들을 정확히 구분하려면 객체 pt1의 정보(주소)도 분명히 전달받아야 구별이 가능할 것이다.


2. this

#include <iostream>

class Point
{
	int x{0};
	int y{0};
public:
	void set(Point* this,int a, int b)
	{
		this->x = a;
		this->y = b;	
	}
};

int main()
{
	Point pt1;
	Point pt2;

	pt1.set(&pt1,10, 20);
	pt2.set(&pt2,10, 20);
}

- 실제로 이렇게 변환되지는 않지만, 멤버 함수 호출 시 위와 같이 객체의 주소가 같이 전달된다.

- 이러한 것을 this call 이라고 한다.

- this는 멤버 함수 안에서 사용가능한 키워드로, 해당 멤버 함수를 호출할 때 사용한 객체의 주소이다.

객체 생성 여부와 별개로 메모리에 할당되는 static member function에서는 this를 사용할 수 없다.

 

2-1. 어셈블리어 레벨에서 확인

- 우리가 실질적으로 보낸 것은 2개지만, 1개의 인자를 더 사용하는 것을 확인이 가능하다. 

 

2-2. this가 사용되는 경우

- 멤버 데이터라는 것을 명확히 하고 싶을때 사용할 수 있다.

- 또한, 멤버 함수가 this 또는 *this를 반환하면, 멤버 함수를 연속적으로 호출할 수 있다.

class Counter
{
	int count{0};
public:
	void reset(int count = 0)
	{
		this->count = count;
	}
	Counter* increment()
	{
		++count;
		return this;
	}
	Counter& decrement() // 참조(Reference)로 반환해야 함에 유의하라
	{
		--count;
		return *this;
	}
};
int main()
{
	Counter c;	
	c.increment()->increment()->increment();
	c.decrement().decrement().decrement();
}

※ 주의할 점은 *this를 반환하는 경우 반드시 참조(Reference)로 반환해야 한다.

※ 이유가 상당히 중요하므로 아래에서 한번 다룬다.

 

2-3. Reference Return 예시

Reference Return을 하는 경우의 예시

- 이를 사용하고 있는 대표적인 케이스가 바로 std::cout이다.

- std::cout << "A" << "B" << "C";의 경우 std::cout.operator<<("A") 가 구현된 것이다.

- 이후, return *this 즉, std::cout가 바로 반환되기 때문에 위와 같이 사용이 가능한 것이다.


3. Reference Return

- 시작에 앞서 아래 그림을 통해 다시 한번 기억해보고자 한다.

- Call by value는 객체의 복사본을 생성한다. f1에서 p1을 parameter로 정의시,객체가 생성될때 복사본을 만든다.

- 반면, Call by Reference는 객체의 복사본이 아닌 기존 객체의 별칭(Alias)를 한다.

 

struct Point
{
	int x;
	int y;
};
Point pt = {1, 2};

Point f1()  // return by value
{
	return pt;
}
Point& f2() // return by reference
{
	return pt;
}
int main()
{
//	f1().x = 10; // 리턴용임시객체.x = 10
	f2().x = 10; // pt.x = 10   ok.
}

 

- 함수가 객체를 값으로 반환하는 경우에는 리턴용 임시객체(temporary)가 생성되어 반환된다.

- return 용도로 생성된 임시객체(temporary)는 함수를 호출하는 문장의 끝에서 파괴된다.

- 따라서 등호의 왼쪽에 놓일 수 없다. 즉, rvalue이다.

Point pt = {1,2};

Point f1()
{
     return pt;
}

- 즉, 이건 얼핏 보기에 큰 문제가 없어보이지만, 실제로는 pt 그 자체를 반환하는게 아니다.

- pt와 동일한 다른 임시 객체를 반환하게 된다.

 

3-1. temporary의 생성과 파괴

- 생성될 때 복사 생성자가 호출되고, 파괴 될 때 소멸자가 호출된다.

#include <iostream>
class Point
{
public:
	int x;
	int y;
	Point(int a, int b) : x(a), y(b)
	{

	}
	Point(const Point& pt) : x(pt.x), y(pt.y)
	{
		std::cout << "Test\n";
	}
};
Point pt{1,2};

Point f1()
{
	return pt;
}
Point& f2()
{
	return pt;
}
int main()
{
	std::cout << f1().x << std::endl;
	std::cout << "------------" << std::endl;
	std::cout << f2().x << std::endl;
}

- 이와 같이 복사 생성자가 실행되는 것을 확인할 수 있다.

 

return by value 에서는 리턴용 임시객체가 반환되는 반면, return by reference는 리턴용 임시객체가 생성되지 않는다.

 

- 복사 생성자에 대해서는 다음 포스트에 다뤄보고자 한다.

- 따라서, 이 내용에서 복사 생성자에 대한 얘기는 이후 포스트를 수정하여 재정리할 것이다.

 

 

 

'C++ > Basic' 카테고리의 다른 글

[C++] Inheritance(상속)  (0) 2024.08.20
[C++] Copy Constructor  (0) 2024.08.19
[c++] const member function, mutable  (0) 2024.08.17
[C++] Static Member  (0) 2024.08.16
[C++] Explicit Constructor  (0) 2024.08.14
Comments