본문 바로가기
C++

[C++] 좌측값과 우측값 그리고 Move, Forward 함수

by k99812 2025. 5. 13.

좌측값과 우측값

 

L-value

이름이 있고 메모리 주소가 있는 값 

 

R-value

일시적인 값, 임시 객체 또는 계산 결과 등

int x = 10;        // x는 lvalue, 10은 rvalue
int y = x + 5;     // y는 lvalue, (x + 5)는 rvalue

 

C++ remove_reference_t 함수

 

remove_reference_t 함수는 레퍼런스를 제거해주는 함수
레퍼런스를 제외한 정확한 타입을 얻기위한 함수

cout << is_same_v<remove_reference_t<int>, int> << "\n";           // true
cout << is_same_v<remove_reference_t<int&>, int> << "\n";          // true
cout << is_same_v<remove_reference_t<int&&>, int> << "\n";         // true

 

 

Forwarding Reference

 

템플릿 함수에서 T&& 형태로 쓰일 때, 좌측값과 우측값 둘다 참조할 수 있다.

 

  • int& : int 타입의 좌측값 참조
  • int&& : int 타입의 우측값 참조
  • T& : 임의 타입의 좌측값 참조
  • T&& : 임의 타입에서 좌측값, 우측값 참조

템플릿이 추론한 타입에 따라 reference collapsing에 의해 좌측값 참조(T&), 우측값 참조(T&&)가 결정된다

  • reference collapsing
    두 개의 참조 타입중 하나라도 좌측값 참조이면 T&&의 타입은 T&이 된다
TYPE referenceCollapsing(TYPE A, TYPE B){
    if( A == LVALUE_REF || B == LVALUE_REF )
        return LVALUE_REF;
    else 
        return RVALUE_REF;
}

출처: https://ozt88.tistory.com/46 [공부 모음:티스토리]

 

예시

template<typename T> void f4(T&& arg) {}

GPT를 이용한 예시

  • f4(0)
    • T가 int로 추론 됨
    • T&& → int&& 가 되어 rvalue 참조가 됨
    • 추가적으로
      f4<int&&>(0)으로 템플릿이 직적접으로 명시된 경우에만 int&&로 추론
      결과적으로 int&& &&가 되어 rvalue 참조가 됨
  • f4(n)
    • T가 int&로 추론 됨
    • T&& → int& &&가 되어 lvalue 참조가 됨

 

함수에서 사용

void print(string& s) { cout << "lvalue\n"; }
void print(string&& s) { cout << "rvalue\n"; }

template<typename T>
void func(T&& val) 
{
    print(forward<T>(val)); // perfect forwarding
}

int main() 
{
    string str = "hi";
    func(str);        // lvalue → lvalue 유지
    func(string("temp")); // rvalue → rvalue 유지
}

 

 

  • void func(T&& val) 
    • 템플릿을 통해 변수를 받음
    • forward 함수를 통해 좌측값 or 우측값을 완벽 전달
      단순히 print(val)로 호출하면 val은 항상 좌측값으로 분류되어
      forward 함수로 좌측값 or 우측값으로 캐스팅
  • void print(string& s)
    void print(string&& s) 
    • 오버로딩을 활용해 좌측값 참조, 우측값 참조 함수를 생성

 

Forward 함수

 

Forward  함수는 T&&로 들어온 변수를 좌측값 or 우측값으로 변환하여 완벽하게 전달하기 위해 사용됨

 

template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept {
    return static_cast<_Ty&&>(_Arg);
}

template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept {
    static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
    return static_cast<_Ty&&>(_Arg);
}

 

좌측값 forward 함수

  • constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept
    • constexpr 
      컴파일 타임 상수를 리턴타입에 넣어 주면서 constexpr 변수에도 사용가능
    • _Ty&&
      좌측값 참조 or 우측값 참조가 될 수 있음
    • remove_reference_t<_Ty>&
      템플릿이 추론한 타입에서 레퍼런스를 제거하여 타입을 선언
      ex) remove_reference_t<int&>& → int& 가 됨
      이로인해 해당 함수는 좌측값 참조를 매개변수로 갖는 함수가 됨
    • noexcept
      함수가 예외를 발생하지 않도록 해줌
  • return static_cast<_Ty&&>(Arg);
    • static_cast<T&&>(Arg)
      Arg는 _Ty& 이므로 static_cast< _Ty& &&> →  _Ty&로 캐스팅 

 

우측값 forward 함수

  • constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept
    • remove_reference_t<_Ty>&&
      ex) remove_reference_t<int&&>&& → int&&
      이로인해 우측값 참조를 매개변수로 갖음
  • return static_cast< _Ty &&>(Arg);
    • static_cast<t&&>(Arg)
      Arg는 _Ty&& 이므로 static_cast<_Ty&& &&> →  _Ty&&로 캐스팅

 

Move 함수

 

Move 함수는 주로 좌측값 변수를 우측값으로 캐스팅할 때 사용됨

 

template <class _Ty>
constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept {
    return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

 

  • constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept
    • remove_reference_t<_Ty>&&
      remove_reference_t로 레퍼런스를 제거해 우측값만 return
    • move(_Ty&& _Arg)
      _Ty&&로 좌측값, 우측값 둘다 변수로 받음
  • return static_cast<remove_reference_t<_Ty>&&>(_Arg);
    • (_Arg)
      _Arg는 좌측값(&) 또는 우측값(&&)
    • static_cast<remove_reference_t<_Ty>&&>
      remove_reference_t를 사용해 레퍼런스를 제거하여 우측값으로 캐스팅 됨
      ex) remove_reference_t<int& or int&&>&& → int&&

 

Move 함수 사용

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main() {
    vector<string> v;
    string str = "test";

    v.push_back(move(str));

    cout << "str: \"" << str << "\"\n";
    cout << "v[0]: \"" << v[0] << "\"\n";
}

출력
str: ""
v[0]: "test"

 

  • move 함수를 통해 str을 우측값으로 캐스팅하여 백터에 push_back
  • str의 소유권이 v로 이동
  • 이때 move는 소유권을 이동시키는게 아닌 우측값 캐스팅만 함
  • 소유권 이전은 push_back 함수에서 진행
    그래서 move(str)만 실행할 경우 str의 내용물이 남아 있음

 

추가적으로

 

noexcept를 붙이는 이유

https://modoocode.com/227

 

씹어먹는 C++ - <12 - 1. 우측값 레퍼런스와 이동 생성자>

모두의 코드 씹어먹는 C++ - <12 - 1. 우측값 레퍼런스와 이동 생성자> 작성일 : 2018-03-24 이 글은 77207 번 읽혔습니다. 이번 강좌에서는 복사 생략 (Copy elision)우측값 레퍼런스 (rvalue referen ce)이동 생성

modoocode.com

 

이동생성자

https://chogyujin-study.tistory.com/42

 

(C++) 이동생성자(Move Constructor)

이동 생성자(Move Constructor)에 대해 공부하겠습니다. 이동생성자(Move Constructor) 이동 생성자는 객체가 살아있지만 안 쓴다고 보장할 수 있는 상황일 때 사용하게 되는 코드입니다. 기존 객체의 주

chogyujin-study.tistory.com