[2026] C++ Rvalue vs Lvalue | 값 범주 가이드
이 글의 핵심
C++ Rvalue vs Lvalue - 값 범주 가이드. C++ Rvalue vs Lvalue의 Lvalue vs Rvalue, Lvalue (좌측값), Rvalue (우측값)를 실전 코드와 함께 설명합니다.
Lvalue vs Rvalue
좌측값과 우측값 구분 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
int x = 10; // x는 lvalue, 10은 rvalue
int& lref = x; // OK: lvalue 레퍼런스
// int& lref2 = 10; // 에러: rvalue를 lvalue 레퍼런스에
int&& rref = 10; // OK: rvalue 레퍼런스
// int&& rref2 = x; // 에러: lvalue를 rvalue 레퍼런스에
Lvalue (좌측값)
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 이름 있고 주소 있음
int x = 10;
int* ptr = &x; // OK
// Lvalue 예시
int x; // 변수
int arr[10]; // 배열
std::string s; // 객체
int& ref = x; // 레퍼런스
*ptr; // 역참조
Rvalue (우측값)
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 임시 값, 주소 없음
// int* ptr = &10; // 에러
// Rvalue 예시
10; // 리터럴
x + y; // 표현식 결과
func(); // 함수 반환값 (비레퍼런스)
std::move(x); // 명시적 rvalue
실전 예시
예시 1: 레퍼런스 바인딩
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
void func(int& x) {
std::cout << "lvalue ref" << std::endl;
}
void func(int&& x) {
std::cout << "rvalue ref" << std::endl;
}
int main() {
int x = 10;
func(x); // lvalue ref
func(10); // rvalue ref
func(std::move(x)); // rvalue ref
}
예시 2: 이동 의미론
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
~Buffer() {
delete[] data;
}
// 복사 생성자 (lvalue)
Buffer(const Buffer& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
std::cout << "복사" << std::endl;
}
// 이동 생성자 (rvalue)
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
std::cout << "이동" << std::endl;
}
};
int main() {
Buffer b1(100);
Buffer b2 = b1; // 복사
Buffer b3 = std::move(b1); // 이동
}
예시 3: std::move
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#include <vector>
#include <string>
int main() {
std::vector<std::string> vec1;
vec1.push_back("Hello");
// 복사
std::vector<std::string> vec2 = vec1;
// 이동
std::vector<std::string> vec3 = std::move(vec1);
// vec1은 이제 비어있음
}
예시 4: 함수 반환
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::string getName() {
return "Alice"; // rvalue
}
int main() {
std::string name = getName(); // 이동 또는 RVO
const std::string& ref = getName(); // 수명 연장
}
값 범주 (C++11)
아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// lvalue: 이름 있음
int x;
// prvalue: 순수 rvalue
10;
x + y;
// xvalue: 만료 예정 lvalue
std::move(x);
static_cast<int&&>(x);
// glvalue: lvalue + xvalue
// rvalue: prvalue + xvalue
자주 발생하는 문제
문제 1: 이동 후 사용
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = std::move(vec1);
// ❌ 이동 후 사용
vec1.push_back(4); // 정의되지 않은 동작
// ✅ 재할당
vec1 = {5, 6, 7}; // OK
문제 2: const와 이동
const std::string s = "Hello";
// std::string s2 = std::move(s); // 복사됨 (이동 안됨)
// const는 이동 불가
문제 3: 반환값 최적화
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
std::string func() {
std::string s = "Hello";
return s; // ✅ 그냥 반환
// return std::move(s); // ❌ RVO 방해
}
문제 4: 레퍼런스 붕괴
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
void func(T&& x) { // Universal Reference
// x는 lvalue 또는 rvalue
}
int y = 10;
func(y); // T = int&
func(10); // T = int
std::forward
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
template<typename T>
void wrapper(T&& arg) {
// ❌ arg는 항상 lvalue
process(arg);
// ✅ std::forward 사용
process(std::forward<T>(arg));
}
FAQ
Q1: Lvalue vs Rvalue?
A:
- Lvalue: 이름 있음, 주소 있음
- Rvalue: 임시 값, 주소 없음
Q2: std::move는?
A: lvalue를 rvalue로 캐스팅.
Q3: 이동 후 상태는?
A: 유효하지만 불확실. 재할당 가능.
Q4: const와 이동?
A: const는 이동 불가. 복사됨.
Q5: 성능 이점?
A: 복사 대신 이동. 큰 객체에서 효과적.
Q6: Rvalue/Lvalue 학습 리소스는?
A:
- “Effective Modern C++”
- “C++ Move Semantics”
- cppreference.com
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 값 카테고리 | “lvalue/rvalue/xvalue” 완벽 정리
- C++ Move 시맨틱스 | “복사 vs 이동” 완벽 이해
- C++ 복사/이동 생성자 | “Rule of Five” 가이드