C++ Rvalue vs Lvalue: A Practical Guide to Value Categories

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:


  • 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.


C++, rvalue, lvalue, value category, move semantics — searches like these should surface this article.


  • C++ value categories
  • C++ move semantics
  • C++ references guide
  • C++ move semantics series
  • C++ algorithm copy