Lvalue, Rvalue, and C++ Value Category
1. 개요
C++11의 핵심 기능인 Rvalue 참조(Rvalue references)와 이동 의미론(Move Semantics)을 이해하려면 Lvalue와 Rvalue의 개념을 명확히 알아야 합니다. 이 문서는 전통적인 Lvalue/Rvalue 개념부터 C++11 이후 확장된 값 카테고리(value categories)까지, 기존의 유용한 예제들을 바탕으로 종합적으로 설명합니다.
2. Lvalue와 Rvalue의 기본 개념
가장 간단한 구분 기준은 "표현식의 주소를 안전하게 얻을 수 있는가?" 입니다.
- Lvalue (Locator Value): 식별 가능한(identifiable) 메모리 위치를 가지는 표현식입니다. 이름이 붙어 있거나, 포인터로 참조할 수 있어 표현식이 끝난 후에도 객체가 살아남습니다.
- Rvalue (Read Value): 식별 가능한 메모리 위치를 가지지 않는 임시적인(temporary) 값입니다. 표현식이 끝나면 바로 소멸됩니다.
int i = 3; // i는 lvalue, 3은 rvalue
int j = i; // i와 j 모두 lvalue.
// 단, j에 i의 '값'을 대입하기 위해 i는 lvalue-to-rvalue 변환을 거침
3. Lvalue/Rvalue 특징과 상세 예제
Lvalue가 되는 경우
- 전위 증가/감소 연산자 (
++i
,--i
)- 연산 후 객체 자신(lvalue)을 반환합니다.
```cpp
int nCount = 0; - +nCount = 5; // 유효. nCount의 값이 5가 됨
- 연산 후 객체 자신(lvalue)을 반환합니다.
- 간접 참조(역참조) 연산자 (
*p
)- 포인터가 가리키는 메모리 위치(lvalue)를 반환합니다.
```cpp
int a[10];
int *p = a; - p = 3; // 유효
- (p + 1) = 4; // 유효. (p+1)은 rvalue이지만, 역참조한 결과 * (p+1)은 lvalue
- 포인터가 가리키는 메모리 위치(lvalue)를 반환합니다.
- 참조를 반환하는 함수 호출
- 함수가 반환하는 참조는 특정 객체(lvalue)를 가리킵니다.
int i, j; int& get_bigger(int& a, int& b) { return (a > b ? a : b); } get_bigger(i, j) = 10; // 유효. i와 j 중 더 큰 객체에 10을 대입
- 함수가 반환하는 참조는 특정 객체(lvalue)를 가리킵니다.
Rvalue가 되는 경우
- 리터럴 (Literals)
3
,3.14
,'a'
,true
등 값 자체는 rvalue입니다.// 3 = i; // 오류: 리터럴은 대입의 대상이 될 수 없음
- 열거형 상수 (Enum constants)
- 열거형의 멤버 또한 rvalue로 취급됩니다.
enum Color { red, green, blue }; // blue = green; // 오류: 열거형 상수는 rvalue
- 열거형의 멤버 또한 rvalue로 취급됩니다.
- 대부분의 이항 연산자 결과
a + b
,a - b
등의 연산 결과는 새로운 임시 값(rvalue)입니다.int m = 1, n = 2; // (m + 1) = n; // 오류: (m+1)의 결과는 rvalue
- 후위 증가/감소 연산자 (
i++
,i--
)- 연산 이전의 값을 복사한 임시 객체(rvalue)를 반환합니다.
int nCount = 0; nCount++ = 5; // 오류: nCount++의 결과는 rvalue
- 연산 이전의 값을 복사한 임시 객체(rvalue)를 반환합니다.
- 주소 연산자 (
&
)의 결과- 주소 '값' 자체는 rvalue입니다.
int n, *p; p = &n; // 유효. &n의 결과를 p에 대입 // &n = p; // 오류. &n의 결과(주소 값)는 rvalue
- 주소 '값' 자체는 rvalue입니다.
- 값을 반환하는 함수 호출
- 함수가 값으로 반환한 객체는 임시 객체(rvalue)입니다.
char* fun() { return "Hello"; } // "Hello"는 수정 불가능한 메모리에 존재 char* q = fun(); // q[0] = 'h'; // 런타임 오류. 문자열 리터럴을 수정하려 함
- 함수가 값으로 반환한 객체는 임시 객체(rvalue)입니다.
4. C++11 값 카테고리: prvalue, xvalue
C++11부터는 값의 특성을 더 명확히 하기 위해 카테고리를 세분화했습니다.
표현식 (expression)
/ \
glvalue rvalue
/ \ / \
lvalue xvalue prvalue
- prvalue (pure rvalue): 순수 rvalue. 리터럴, 임시 객체 등 전통적인 rvalue 대부분이 해당됩니다.
- xvalue (eXpiring value): "곧 소멸될 값". 자원을 이동시킬 수 있는 lvalue처럼 취급됩니다.
std::move
의 결과가 대표적인 xvalue입니다. - glvalue (generalized lvalue):
lvalue
+xvalue
. 정체성(identity)이 있는 표현식. - rvalue:
prvalue
+xvalue
. 이동이 가능한(movable) 표현식.
5. std::move
와 이동 의미론
std::move
는 lvalue를 xvalue로 캐스팅하는 함수입니다. 이 캐스팅을 통해 컴파일러는 해당 객체를 "곧 사라질 것이니 자원을 훔쳐가도 좋다"고 인식하게 됩니다.
std::string s1 = "hello";
std::string s2 = std::move(s1); // 1. std::move(s1)이 s1을 xvalue로 캐스팅
// 2. s2는 이동 생성자(std::string(std::string&&))를 호출
// 3. s1의 내부 포인터를 s2로 이동(자원 절도)
// 4. s1은 비어있는 "유효하지만 명시되지 않은 상태"가 됨
std::move
자체는 아무것도 옮기지 않습니다. 단지 rvalue 참조를 받는 이동 생성자나 이동 대입 연산자가 호출되도록 유도할 뿐입니다.
6. 참조 바인딩 규칙
- Lvalue 참조 (
&
): Lvalue만 바인딩할 수 있습니다. const
Lvalue 참조 (const &
): Lvalue와 Rvalue 모두 바인딩할 수 있어 매우 유용합니다.- Rvalue 참조 (
&&
): Rvalue(prvalue, xvalue)만 바인딩할 수 있습니다.
std::string s = "text";
std::string& ref1 = s; // OK: Lvalue -> Lvalue 참조
// std::string& ref2 = "text"; // 오류: Rvalue -> Lvalue 참조
const std::string& ref3 = s; // OK: Lvalue -> const Lvalue 참조
const std::string& ref4 = "text"; // OK: Rvalue -> const Lvalue 참조
// std::string&& ref5 = s; // 오류: Lvalue -> Rvalue 참조
std::string&& ref6 = std::move(s); // OK: xvalue(Rvalue) -> Rvalue 참조
중요: 이름이 부여된 Rvalue 참조 변수 자체는 Lvalue입니다. (주소를 가질 수 있으므로)
7. 요약
카테고리 | 정체성(주소) | 이동 가능 | 대표 예시 |
---|---|---|---|
Lvalue | O | X | 변수, *p , ++x |
prvalue | X | O | 리터럴, x+y , x++ |
xvalue | O | O | std::move(x) |
8. 참고 자료
'개발 > C++ (98,03,11,14,17,20,23)' 카테고리의 다른 글
Modern C++ : rvalue reference (1) | 2025.08.16 |
---|---|
Modern C++ : lvalue rvalue reference (98, 11) (2) | 2025.08.15 |
Modern C++ : dynamic heap memory allocation (98, 11, 14) (1) | 2025.08.14 |
Modern C++ : pointer and address (98, 11) (3) | 2025.08.13 |
Modern C++ : const reference and value semantics (98, 11) (1) | 2025.08.12 |
Modern C++ : string vs std::string vs std::array<char, N> (98, 11, 17) (1) | 2025.08.11 |
Modern C++ : array vs std::array (98, 11) (1) | 2025.08.10 |
Modern C++ : anonymous namespace (98, 11, 17) (2) | 2025.08.09 |