C++ Rvalue vs Lvalue: A Practical Guide to Value Categories
이 글의 핵심
A clear guide to C++ lvalues, rvalues, and value categories—with practical examples and common pitfalls.
Lvalue vs Rvalue
Lvalues and rvalues
int x = 10; // x is an lvalue; 10 is an rvalue
int& lref = x; // OK: lvalue reference
// int& lref2 = 10; // Error: cannot bind rvalue to lvalue reference
int&& rref = 10; // OK: rvalue reference
// int&& rref2 = x; // Error: cannot bind lvalue to rvalue reference
Lvalue
// Named and has an address
int x = 10;
int* ptr = &x; // OK
// Lvalue examples
int x; // Variable
int arr[10]; // Array
std::string s; // Object
int& ref = x; // Reference
*ptr; // Dereference
Rvalue
// Temporary; no stable address for the value itself
// int* ptr = &10; // Error
// Rvalue examples
10; // Literal
x + y; // Result of expression
func(); // Return by value (non-reference)
std::move(x); // Explicit rvalue (xvalue)
Practical examples
Example 1: Reference binding
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
}
Example 2: Move semantics
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s) {
data = new int[size];
}
~Buffer() {
delete[] data;
}
// Copy constructor (lvalue)
Buffer(const Buffer& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
std::cout << "copy" << std::endl;
}
// Move constructor (rvalue)
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
std::cout << "move" << std::endl;
}
};
int main() {
Buffer b1(100);
Buffer b2 = b1; // copy
Buffer b3 = std::move(b1); // move
}
Example 3: std::move
#include <vector>
#include <string>
int main() {
std::vector<std::string> vec1;
vec1.push_back("Hello");
// Copy
std::vector<std::string> vec2 = vec1;
// Move
std::vector<std::string> vec3 = std::move(vec1);
// vec1 is now empty
}
Example 4: Function return
std::string getName() {
return "Alice"; // rvalue
}
int main() {
std::string name = getName(); // move or RVO
const std::string& ref = getName(); // lifetime extension
}
Value categories (C++11)
// lvalue: has a name
int x;
// prvalue: “pure” rvalue
10;
x + y;
// xvalue: expiring lvalue
std::move(x);
static_cast<int&&>(x);
// glvalue: lvalue + xvalue
// rvalue: prvalue + xvalue
Common pitfalls
Pitfall 1: Using an object after a move
std::vector<int> vec1 = {1, 2, 3};
std::vector<int> vec2 = std::move(vec1);
// ❌ Using after move
vec1.push_back(4); // Undefined behavior
// ✅ Reassign
vec1 = {5, 6, 7}; // OK
Pitfall 2: const and move
const std::string s = "Hello";
// std::string s2 = std::move(s); // copies (no move from const)
// const cannot be moved from
Pitfall 3: Return value optimization
std::string func() {
std::string s = "Hello";
return s; // ✅ plain return
// return std::move(s); // ❌ can break NRVO
}
Pitfall 4: Reference collapsing
template<typename T>
void func(T&& x) { // forwarding reference
// x is an lvalue or rvalue in the body
}
int y = 10;
func(y); // T = int&
func(10); // T = int
std::forward
template<typename T>
void wrapper(T&& arg) {
// ❌ arg is always an lvalue in the body
process(arg);
// ✅ use std::forward
process(std::forward<T>(arg));
}
FAQ
Q1: Lvalue vs rvalue?
A:
- Lvalue: Has a name and a stable address.
- Rvalue: Temporary; typically no usable address for the value itself.
Q2: What does std::move do?
A: It casts an lvalue to an rvalue (xvalue).
Q3: State after a move?
A: Valid but unspecified; you can reassign.
Q4: const and move?
A: You cannot move from const; the operation will copy.
Q5: Performance benefit?
A: Avoid deep copies by moving; most effective for large objects.
Q6: Learning resources?
A:
- *Effective Modern C++
- C++ Move Semantics (David Stone)
- cppreference.com
Related reading (internal links)
- C++ value categories
- C++ move semantics
- C++ copy/move constructors (Rule of Five)
Practical tips
Tips you can apply at work.
Debugging
- When something breaks, check compiler warnings first.
- Reproduce with a minimal test case.
Performance
- Do not optimize without profiling.
- Define measurable targets first.
Code review
- Review common review feedback in advance.
- Follow your team’s conventions.
Practical checklist
When applying these ideas:
Before coding
- Is this the best fix for the problem?
- Can teammates maintain this code?
- Does it meet performance requirements?
While coding
- Are all warnings addressed?
- Are edge cases considered?
- Is error handling appropriate?
At review
- Is intent clear?
- Are tests sufficient?
- Is documentation adequate?
Use this checklist to reduce mistakes and improve quality.
Keywords (search)
C++, rvalue, lvalue, value category, move semantics — searches like these should surface this article.
Related posts
- C++ value categories
- C++ move semantics
- C++ references guide
- C++ move semantics series
- C++ algorithm copy