L_value와 R_value

양비밀이·2020년 8월 17일
0

L-value & R-value

L value는 왼쪽에 있는거, R value는 오른쪽에 있는거... 가 아니다.

L-value는 나중에 다시 부를 수 있는 것. R-value는 그렇지 못한 것. 으로 구분하면 쉽다.

예를 들어,

int a=0;
int b=a;

에서 a 는 L-value이다. 0은 다시 부를 수 없으므로 R-value이다.
b역시 L이겠지?

R-value는 임시적으로 생성된 값이라고 볼 수 있다.

그러면 대체 왜 이런걸 구분할까?

R-value를 잘 이용하면 메모리를 효율적으로 사용할 수 있기 때문이다.

예를 들어, 어떤 값이 R-value이고 이 값을 전달하기 위해 몇 개의 변수를 거친다고 가정해보자.

이 값을 전달하기 위해 변수를 거칠 때 마다 값을 복사할 필요가 있을까? 어쩌피 임시적으로 생성된 값이므로, 값을 복사하는게 아니라 최종 목적지가 이 값을 가리키기만 하면 될 것이다.

따라서 R-value를 이용하면 값을 복사하지 않고 여러 변수들을 거쳐 값을 전달할 수 있다. (아래 예제를 보자)

Cpp에서 L-value는 &로 R-value는 &&로 나타낸다.

std::move(arg)

std::move(arg) 를 이용하여 L-value인 arg를 R-value로 바꿀 수 있다.

이 때, 기존의 L-value는 더이상 힙 영역을 참조하지(가리키지) 않는다.

예를 들어,

string s = "Hello World!";
string b = std::move(s);

이런 코드가 있다면, b는 heap영역의 문자열 "Hello World!"를 가리키지만 s는 아무것도 가리키지 않는다.

R-value 사용의 예

다음의 코드를 보자,

#include <string>
using namespace std;

void byValue(string s)
{
	string b = s;
}
void byLRef(string & s)
{
	string b = s;
}
void byRRef(string && s)
{
	string b = std::move(s);
}

int main()
{
	string str = "Hello World!";
    byValue(str); 	// 1
    byLRef(str);	// 2
    byRRef(std::move(str)); // 3
    return 0;
}

우선 string str = "Hello World!"
의 코드에서 힙 영역에 메모리가 할당되고 문자열이 들어간다.

여기까지의 메모리 구조는 다음과 같을 것이다.

main의 1번을 보자

우선 매개변수 s가 스택에 들어가고, s는 a의 값을 복사하여 heap에 넣을 것이다.
그리고 다시 b가 스택에 들어가고, b는 s의 값을 복사하여 heap에 넣고 가리킨다.

메모리 구조는 다음과 같다.

총 2번의 copy가 발생한 것을 알 수 있다.

main의 2번을 보자

s는 referece value이다. 따라서, heap에 문자열을 복사하는게 아니라 a가 가리키는 영역을 그대로 가리킨다.

이후 b가 스택에 추가되고 a와s가 가리키는 영역을 복사한 뒤 가리킨다.

메모리 구조는 다음과 같다.

총 1번의 copy가 발생한다.

main의 3번을 보자

byRRef의 매개변수에 str을 그대로 넣는다면 오류가 발생한다. (L-value를 R-value에 넣었기 때문)

따라서, std::move를 이용해 R-value로 바꿔주거나, "Hello World"를 직접 넣어줘야 한다.

매개변수에 std::move(str)를 넘겨주면 byRRef(string && s)의 s값이 "Hello World!" 문자열을 가리키게 되고, 기존의 str은 더 이상 "Hello World"를 가리키지 않는다.

이후 다시 string b = std::move(s)를 통해 s는 더 이상 "Hello World!"를 가리키지 않고, b가 이 영역을 가리키게 된다.

이를 그림으로 나타내면 다름과 같다.

void byRRef(string && s)
{
	cout << "BEFORE move(), s : " << s << endl;
	string b = std::move(s);
	cout << "AFTER move(), s : " << s << endl;
	cout << "b : " << b << endl;
}

int main()
{
	string str = "Hello World!";

	cout << "BEFORE move(), str : " << str << endl;
	byRRef(std::move(str)); // 3
	cout << "AFTER move(), str : " << str << endl;
	
	system("pause");
	return 0;
}

위와 같은 코드를 작성하여 변수의 값들을 확인해보면, s와 str에는 더 이상 아무런 값도 남아 있지 않음을 (더 이상 heap을 가리키지 않음) 알 수 있다.

참고로 string b = std::move(s)가 아닌 string b = s; 라면 copy가 일어난다.

비록 s가 R-value로 초기화(?) 됐어도 이후에 s를 통해서 접근이 가능하니 L-value가 되기 때문이다. 그래서 std::move를 다시 한번 사용해야 한다.

<내용 추가 : 이동생성자, 복사생성자>

0개의 댓글