먼저 전통적인 lvalue와 rvalue는 다음과 같은 개념이다.
lvalue: 객체를 참조하는 표현식으로 특정 메모리 위치를 갖고 있다.
rvalue: lvalue가 아닌 모든 것.
즉, 일반 변수처럼 이름으로 구분 가능하면 lvalue이고,
그렇지 않은 리터럴 상수같은 것들을 rvalue라고 볼 수 있으며,
아래와 같이 좀 더 엄밀하게 구분 할 수 있다.
lvalue | rvalue |
단항 & 의 피연산자 | 리터럴 상수 |
단항 * 의 피연산자 | 열거 형 상수 |
prefix 증가/감소 연산의 결과 | 산술 연산의 결과 |
참조자 반환 시 | 단항 & 연산의 결과 |
단항 * 의 연산의 결과 | |
참조자 반환이 아닌 경우 | |
postfix 증가/감소 연산의 결과 |
여기에 더해서 C++ 표현식은 다음과 같은 트리구조로 나뉘어 진다.
이와 같이 나누는 기준은 아래와 같다.
1. 구분이 가능한 지에 대한 여부.
--> 식별가능한 이름(주소)를 갖는다.
2. 이동이 가능한 지에 대한 여부.
--> 이동생성자, 이동대입연산자 지원을 한다.
--> 생성자와 대입연산자에 인자 형식이 rvalue인 것.
1번 조건 만을 충족하는 것이 glvalue, lvalue 이고,
2번 조건 만을 충족하는 것이 rvalue, prvalue 이다.
또한, 이러한 lvalue와 rvalue에 대한 참조자는 아래와 같이 선언 할 수 있다.
lvalue -> type& 변수이름 = lvalue;
-- 이미 존재하는 객체에 대한 alias로 사용할 수 있다.
rvalue -> type&& 변수이름 = rvalue;
-- 임시 객체의 Lifetime 확장 용도로 사용. const lvalue 참조자로 이것이 가능하나 수정이 불가능 한 반면,
-- rvalue 참조자로는 수정이 가능하다는 차이점이 존재.
그리고 1번 조건과 2번 조건 모두를 만족하는 것이 xvalue 이다.
즉, lvalue 값을 이동 시킬 수 있다는 것인 데, 아래 예제로 설명하자면,
std::string str = "ABC";
// "ABC"는 이 순간에만 메모리를 소유 하는 rvalue, 즉, 대입 표현식 우측에만 사용 되며,
// 추후 어떤 이름을 갖고 재 접근 할 수 없다.
// str 은 대입 연산자 왼편에 사용되는 lvalue로 내부적으로 "ABC"를 저장하는 메모리의 데이터를,
// 별도에 지시가 없는한 영구적으로 소유하여 추후에도 str이라는 이름으로 접근 가능 하다.
이제 str이 갖고 있는 문자열을 다른 lvalue로 옮긴 다면,
즉, str이 접근 가능한 메모리 영역이 다른 lvalue에 rvalue 형식으로 사용 되어, 그 순간에만 접근 가능하고,
이후 str로 접근 할 수 없게 된다면,
str은 xvalue로 사용되는 것이다.
아래는 간단한 예제이다.
#include <iostream>
#include <utility>
void f(int& x) {
std::cout << "lvalue reference overload f(" << x << ")\n";
}
void f(const int& x) {
std::cout << "lvalue reference to const overload f(" << x << ")\n";
}
void f(int&& x) {
std::cout << "rvalue reference overload f(" << x << ")\n";
}
int main() {
int i = 1;
const int ci = 2;
f(i); // calls f(int&)
f(ci); // calls f(const int&)
f(3); // calls f(int&&)
// would call f(const int&) if f(int&&) overload wasn't provided
f(std::move(i)); // calls f(int&&)
// rvalue reference variables are lvalues when used in expressions
int&& x = 1;
f(x); // calls f(int& x)
f(std::move(x)); // calls f(int&& x)
}
주석에 명시된 것 처럼, rvalue 참조자가 표현식에서 사용된 경우에는 lvalue로 처리되니,
rvalue 인자로 넘기고 싶은 경우에는 std::move() 를 사용해서 넘겨야 한다.
https://en.cppreference.com/w/cpp/language/reference
'C++' 카테고리의 다른 글
std::unique_ptr (0) | 2021.10.27 |
---|---|
std::find() (0) | 2021.10.27 |
형변환 연산자 (0) | 2021.10.21 |
포인터 (0) | 2021.10.21 |
변수 (0) | 2021.10.21 |