본문 바로가기
Language/C++

lvalue와 rvalue + 우측값 참조법

by W00gie 2021. 12. 21.

* lvalue와 rvalue란?

 

C++ 내에서 모든 식들은 카테고리라는 부가적인 정보를 포함하고 있다. l-value와 r-value가 이에 해당되는데,

 

lvalue는 읽고 대입할 수 있는 값, 식(expression)에서 좌측에 존재하기에 좌측 값(left value)라 불린다.

이름을 가진 대부분의 값들은 lvalue에 포함된다. 이름을 가졌다는 것은 메모리를 가지고 있다는 뜻이고 이는 대입할 수 있는 주소값을 가지고 있다는 뜻이기 때문이다.

 

rvalue는 읽기만 할 수 있는 값, 식(expression)의 우측에 존재하기에 우측 값이라 불린다.

lvalue와 반대로 주소값을 가지지 않고 명확한 실체가 존재하지 않는 값이라 할 수 있다.

대표적으로 상수 혹은 연산자, 람다 등이 포함된다.

 

실체를 가지지 않는 rvalue에 특정 값을 대입하려 시도하면 컴파일러는 이를 분별해 에러로 취급한다.

void main() {
	int a;
	a = 3; // OK
	2 = 3; // Error
}

 

 

이 외에도 C++에는 보다 세부적으로 총 5개의 카테고리가 존재하지만 이번 포스팅에서 이는 제외한다.

(아래 출처에 좋은 내용으로 정리되어있으니 참고하면 큰 도움이 된다.)

출처 - https://modoocode.com/294

 

 

 


rvalue의 용도

 

lvalue와 rvalue의 정의를 확인했다. 이러한 타입들을 몰라도 그 동안 코드를 잘 작성해왔는데 이러한 타입들은 어디에 이용될 수 있을까? 핵심은 rvalue를 참조하여 대입과정에서 임시객체 생성을 생략하는 것이다. 대입식에서 계산된 식이 l-value에 대입되는 과정에서는 그 동안 하나의 atomic한 연산이라 생각할 수 있었지만, 내부적으로는 복사 이후 파괴 과정이 진행된다.

void main()
{
    int b = 2;
    int c = 3;
    int a = b + c ;  // b+c에 대한 결과의 '임시객체' 생성 이후 값 복사, 이후 임시객체 파괴
}

b+c 의 연산과정이 a에 대입된다는 사실을 전제하에 진행된다면 b+c의 결과를 임시객체에 저장하고 다시 a에 복사하는 과정이 프로그래밍상의 오버헤드라는 사실을 알 수 있다. 당장은 int 변수 하나의 크기지만 크기가 큰 객체를 일일히 복사한다면 큰 손해일 것이다. 이러한 과정을 복사생략(copy ellision)을 통해 해결할 수 있다. a에서 생성되는 생성자에 값을 복사하는것이 아니라 b+c의 결과 주소값을 a의 생성자에 그대로 이동시킨다면 복사과정을 생략할 수 있을 것이다. 하지만 어떻게 실체가 없는 rvalue를 a의 생성자에서 참조할 수 있을까?

 

C++ 11에서부터는 우측값 참조 연산자를 제공한다. ( && )

실체, 주소값이 없는 rvalue에 대해서 별명을 붙여준다 생각하면 이해하기 쉽다.

void main() {
	
	int&& ref = 3;
	ref = 2; // OK
}

내부적으로는 메모리를 할당하여 rvalue인 3을 대입하는 역할을 한다. 이를 통해 b+c 과정에서 나온 우측값에 대해 생성자를 적용할 수 있고, 이러한 생성자를 이동 생성자라 부른다. 추후 포스팅에는 이러한 이동과정을 도와 lvalue도 우측값으로 활용할 수 있는 이동 생성자와 이동 가능여부를 확인할 수 있는 move 함수 대해서도 공부해볼 계획이다.

template <class T>
constexpr typename std::remove_reference<T>::type&& move(T&& t) noexcept;