본문 바로가기
프로그래밍/공부

[C++] & Reference

by Sik.K 2023. 4. 22.

연산자 &는 논리 연산자로 분류가 된다. 하지만 변수 앞에 붙을 경우 의미가 달라진다.

 

int main()
{
	int a = 10;

	cout << &a << endl;

	return 0;
}

 

 

위 코드를 실행한 결과이다. 이상하다. a에 10이라는 값을 담고 출력을 하면 원래는 10이 나와야 한다. 그런데 앞에 &가 붙으니 이상한 결과가 나왔다. 대체 저 값은 무엇일까?

 

답은 바로 주소이다. 해당 변수가 메모리 상에 할당 받은 주소를 의미한다. 즉 연산자 &는 주소를 가져오는 연산자라고 할 수 있다.

 

하지만 또 다른 용도로 사용이 가능하다.

 

void function(int& num1, int& num2)
{
	if (num1 > num2)
		++num1;
	else if (num1 < num2)
		++num2;
}

int main()
{
	int a = 10;
	int b = 5;

	function(a, b);

	cout << a << endl;
	cout << b << endl;

	return 0;
}

 

위 함수를 실행하면 결과는 어떨까? 본래라면 함수에 주소가 아닌 일반 변수를 매개변수로 주게 되면 값을 복사해서 넘겨주는 것이기 때문에 함수 내부에서 값을 증가 시켜도 외부의 값은 변하지 않는다. 이 방식을 Call By Value라고 한다.

 

 

하지만 이상하게도 컴파일 후 확인을 해보니 a의 값이 증가한 상태였다. 이게 어떻게 된 일일까. 답은 매개변수 타입으로 지정해둔 int&에 있다.

 

&는 주소를 가져오는 기능도 있지만, 자료형에 붙이게 될 경우 참조 혹은 별명을 짓는 기능을 한다.

int main()
{
	int a = 10;
	int& b = a;

	b = 5;

	cout << a << endl;

	return 0;
}

 

 

a라는 변수를 정의하고, int의 레퍼런스로 b를 선언하고 a를 지정한다. 이렇게 하면 a의 메모리 공간을 b라는 이름으로도 사용하겠다는 의미가 되어 b를 수정해도 a의 값이 수정이 된다.

 

때문에 함수에서 매개변수로 참조자를 설정하면 함수가 호출될 때 넘겨주는 변수의 공간을 매개변수의 이름으로 사용하겠다는 의미로 받아들이기 때문에 함수 내부에서 값을 수정하면 외부에서도 변경이 되는 것을 확인할 수 있다. 이 방식을 Call by Reference라고 한다.

 

그런데 공부하다 보면 묘한 경우가 생긴다. 바로 rValue의 참조자이다. 일반적으로 참조자 &는 lValue에 설정이 된다. 왜냐하면 int& a = b의 경우 int형 변수 b의 주소를 a라고 부르겠다는 의미이기 때문이다.

 

특정 함수를 호출하는 코드를 작성한다고 가정해보자.

 

 

#include <string>
#include <iostream>

using namespace std;

storeByValue(string s)
{
    string b = s;
}

storeByLRef(string& s)
{
    string b = s;
}

storeByRRef(string&& s)
{
    string b = move(s);
}

int main()
{
    string a = "abc";
    storeByValue(a);
    storeByLRef(a);
    storeByRRef(move(a));
    
    return 0;
}

스트링 자료형 a에는 "abc"라는 값이 들어갔다. 이후에 각각 세 개의 함수를 호출한 뒤 결과를 메모리 구조로 표현하면 다음과 같다.

 

레퍼런스에 따른 메모리 구조

순서대로 매개변수를 값 복사, Lvalue 레퍼런스, Rvalue 레퍼런스로 호출한 결과이다. 왜 이런 결과가 나왔을까?

 

우선 매개변수의 값을 그저 복사한 결과로는 main 함수의 a의 값을 복사해서 매개변수 s를 생성한 것이기 때문에 a와 s의 공간이 모두 메모리에 할당된 상태이다. 여기서 마지막 b의 공간까지 할당하여 값을 복사하였기 때문에 실질적인 데이터 "abc"는 두 번 복사된 것이다.

 

그렇다면 다음 Lvalue 레퍼런스로 호출한 결과를 보자.

 

우선 변수 a를 생성하고, 매개변수로 Lvalue 레퍼런스 s를 생성하였기 때문에 s는 a와 같은 공간을 가리킨다. 그 이후 변수 b를 생성하여 값을 복사한 것이기 때문에 실질적인 데이터 "abc"는 한 번 복사된 것이다.

 

마지막 Rvalue 레퍼런스를 살펴보자.

 

제일 처음 변수 a를 생성, 후에 Rvalue 레퍼런스로 값을 넘겨주기 위해 move 함수를 통해 a를 Rvalue 레퍼런스로 전환, 매개변수로 받은 Rvalue 레퍼런스 s는 다시 move 함수를 사용하고 b에 값을 넘겨준다.

 

여기서 이상한 점이 하나 있다. 이미 Rvalue 레퍼런스로 전환된 a를 받은 s는 다시 move 함수를 사용한다. 이유는 처음 매개변수로 받아온 것은 Rvalue 레퍼런스이지만 함수 내부에서는 Lvalue처럼 몇 번이고 사용할 수 있기 때문에 우리는 이 부분으로 Rvalue 레퍼런스로 넘겨준 변수는 함수 내부에서 Lvalue처럼 작용한다는 것을 알 수 있다.

 

때문에 move 함수를 통해 다시 Rvalue로 전환해주어 b에게 넘겨주면 결국 실질적인 데이터 "abc"는 값 복사가 일어나지 않고 모든 변수가 하나를 가리키는 것을 알 수 있다.

 

처음에는 왜 Rvalue 레퍼런스를 사용하는지 이해가 가지 않았는데 메모리 관리적인 측면에서 훨씬 더 효율적이라는 것을 알게 되었다. 차후 큰 데이터를 매개변수로 넘겨줄 때 함수 호출 이후 사용하지 않을 데이터라면 Rvalue 레퍼런스로 넘겨주는 것이 효율적이라는 것을 배울 수 있다.

 

'프로그래밍 > 공부' 카테고리의 다른 글

[Graphics] 정점, 폴리곤, 메시  (0) 2023.05.08
[C++] 포인터  (0) 2023.05.04
[C++] Lvalue & Rvalue  (0) 2023.04.22
[C++] 바이트 패딩  (0) 2023.04.20
[Game] DirectX11 모작 - 포켓몬스터 - 1  (0) 2023.04.02

댓글