* 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개의 카테고리가 존재하지만 이번 포스팅에서 이는 제외한다.
(아래 출처에 좋은 내용으로 정리되어있으니 참고하면 큰 도움이 된다.)
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;
'Language > C++' 카테고리의 다른 글
이동생성자와 보편참조법(universal ref) (0) | 2022.01.07 |
---|---|
enable_shared_from_this의 사용법 (0) | 2022.01.04 |
스마트 포인터 (Smart Pointer) (0) | 2021.12.17 |
[자료형] char 자료형으로 for 반복문을 실행하면 어떻게 될까? (0) | 2021.10.26 |
Sort 함수의 커스터마이징, 특정 조건 정렬 (0) | 2021.10.04 |