본문 바로가기
개발/C++ (98,03,11,14,17,20,23)

Modern C++ : lvalue rvalue reference (98, 11)

by snowoods 2025. 8. 15.

Modern C++

LValue and RValue References (98, 03, 11)

 

개요

C++에서 모든 표현식(expression)은 "LValue" 또는 "RValue"라는 두 가지 기본 속성 중 하나를 가집니다. 이 둘을 구분하는 것은 함수 오버로딩, 이동 의미론(move semantics), 완벽한 전달(perfect forwarding)과 같은 C++의 고급 기능을 이해하고 효율적인 코드를 작성하는 데 매우 중요합니다.

  • LValue (Locator Value): 메모리 상의 위치를 가지며, 이름으로 참조할 수 있는 객체입니다. 등호(=)의 왼쪽에 올 수 있습니다. (예: 변수)
  • RValue (Right Value): 임시적인 값으로, 메모리 상의 특정 위치를 가지고 있지 않습니다. 등호(=)의 오른쪽에만 올 수 있습니다. (예: 리터럴, 함수 반환 값)

 

C++ 버전별 주요 키워드 도입 시기

  • C++98/03: LValue 참조 (&)가 도입되었습니다.
  • C++11: RValue 참조 (&&), std::move, std::forward가 도입되어 이동 의미론과 완벽한 전달이 가능해졌습니다.

 

내용 설명

1. 값에 의한 전달 (Pass by Value)

  • void func(int v)
  • 값을 복사하여 새로운 객체를 생성합니다.
  • 원본 데이터는 절대 수정되지 않습니다.
  • LValue, RValue 모든 타입의 인자를 받을 수 있습니다.

2. 비상수 LValue 참조 (Non-const LValue Reference)

  • void func(int& v)
  • 객체의 별칭(alias)으로 동작하며, 복사가 발생하지 않습니다.
  • 참조를 통해 원본 객체를 수정할 수 있습니다.
  • 수정 가능한 LValue만 인자로 받을 수 있습니다. (const LValue나 RValue는 전달 불가)

3. 상수 LValue 참조 (const LValue Reference)

  • void func(const int& v)
  • 비상수 LValue 참조처럼 복사 없이 객체를 참조하지만, 참조를 통해 값을 수정할 수 없습니다.
  • LValue, RValue 모든 타입의 인자를 받을 수 있습니다.
  • RValue를 인자로 받으면, 임시 객체의 생명주기가 참조가 유효한 동안으로 연장됩니다.

4. RValue 참조 (RValue Reference)

  • void func(int&& v)
  • C++11에서 도입되었으며, 소멸될 예정인 임시 객체(RValue)를 참조합니다.
  • 이동 의미론(Move Semantics)을 구현하는 데 사용되며, 불필요한 복사를 피하고 리소스 소유권을 효율적으로 이전할 수 있습니다.

 

예제 코드

#include <iostream>

// 값에 의한 전달 (Pass by Value)
void copy_by_value(int v) // 입력 전용
{
    std::cout << "copy_by_value: " << v << std::endl;
}

// 상수 값에 의한 전달 (Pass by const Value)
void copy_by_value_const(const int v) // 입력 전용
{
    std::cout << "copy_by_value_const: " << v << std::endl;
}

// 비상수 참조 (Non-const Reference)
void reference(int &v) // 입출력 가능
{
    std::cout << "reference: " << v << std::endl;
    v = v * 2; // 원본 값 수정 가능
}

// 상수 참조 (const Reference)
void const_reference(const int &v) // 입력 전용
{
    std::cout << "const_reference: " << v << std::endl;
    // v = 10; // 컴파일 오류: 상수 참조는 수정 불가
}

int main()
{
    int lvalue = 2;
    const int lvalue_const = 2;

    std::cout << "--- 값에 의한 전달 ---" << std::endl;
    copy_by_value(lvalue);           // OK: LValue 전달
    copy_by_value(lvalue_const);     // OK: const LValue 전달
    copy_by_value(2);                // OK: RValue 전달

    std::cout << "\n--- 상수 값에 의한 전달 ---" << std::endl;
    copy_by_value_const(lvalue);     // OK: LValue 전달
    copy_by_value_const(lvalue_const); // OK: const LValue 전달
    copy_by_value_const(2);          // OK: RValue 전달

    std::cout << "\n--- 비상수 참조 ---" << std::endl;
    reference(lvalue);               // OK: LValue 전달. lvalue는 이제 4가 됨
    // reference(lvalue_const);      // 오류: const LValue는 비상수 참조로 전달 불가
    // reference(2);                 // 오류: RValue는 비상수 참조로 전달 불가
    std::cout << "lvalue after reference call: " << lvalue << std::endl;

    std::cout << "\n--- 상수 참조 ---" << std::endl;
    const_reference(lvalue);         // OK: LValue 전달 (현재 값 4)
    const_reference(lvalue_const);   // OK: const LValue 전달
    const_reference(2);              // OK: RValue 전달 (임시 객체 생성)

    return 0;
}

 

실행 결과

--- 값에 의한 전달 ---
copy_by_value: 2
copy_by_value: 2
copy_by_value: 2

--- 상수 값에 의한 전달 ---
copy_by_value_const: 2
copy_by_value_const: 2
copy_by_value_const: 2

--- 비상수 참조 ---
reference: 2
lvalue after reference call: 4

--- 상수 참조 ---
const_reference: 4
const_reference: 2
const_reference: 2

 

활용팁

  • 이동 의미론 (Move Semantics): std::move를 사용하여 LValue를 RValue처럼 취급하게 만들 수 있습니다. 이를 통해 임시 객체가 아닌 객체의 리소스(예: 동적 할당된 메모리)를 복사 없이 '이동'하여 성능을 향상시킬 수 있습니다. std::vector, std::string, std::unique_ptr와 같은 표준 라이브러리 컨테이너에서 널리 사용됩니다.
  • 완벽한 전달 (Perfect Forwarding): std::forward는 템플릿 함수에서 인자의 값 카테고리(LValue 또는 RValue)를 그대로 유지하면서 다른 함수로 전달할 때 사용됩니다. 이를 통해 불필요한 복사를 방지하고, 팩토리 함수나 래퍼 함수를 효율적으로 작성할 수 있습니다.