C++ 중급 - R-value와 R-value Reference

타입·2024년 2월 20일
0

C++ 공부

목록 보기
12/17

l-value vs r-value

표현식(expression)이 등호 왼쪽에 놓일 수 있으면 lvalue, 놓일 수 없으면 rvalue

  • lvalue
    등호(=)의 왼쪽에 올 수 있음
    이름이 있고(데이터 메모리 차지), 단일식을 벗어나서 사용 가능
    주소 연산자로 주소를 구할 수 있음
    참조를 반환하는 함수, 문자열 literal

  • rvalue
    등호(=)의 왼쪽에 올 수 없음
    이름이 없고, 단일식에서만 사용
    주소 연산자로 주소를 구할 수 없음
    값을 반환하는 함수, 실수/정수 literal, 임시객체(temporary)

int x = 10;
int  f1() { return x;} // "10" 을 반환
int& f2() { return x;} // x의 별명 반환 

int main()
{
	int v1 = 0, v2 = 0;

	v1 = 10; // ok     v1 : lvalue
//	10 = v1; // error  10 : rvalue 
	v2 = v1;

	int* p1 = &v1; // ok
//	int* p2 = &10; // error

//	f1() = 20; // 10 = 20  error
	f2() = 20; // x = 20   ok

	const int c = 10;
//	c = 20; // error, 상수는 rvalue가 아님, immutable lvalue

//	Point(1,2).set(10,20); // 임시객체는 상수가 아님

	10 = 20; // error. 10은 lvalue 가 아님
	"aa"[0] = 'x'; // lvalue 문제가 아니라, const char[3]이므로 컴파일 에러
}
  • lvalue, rvalue는 객체, 변수에 부여되는 속성이 아닌 표현식에 부여되는 속성
    표현식: 하나의 값을 만들어내는 코드 집합
int main()
{
	int n = 3;

	n = 10;			// ok
	n + 2 = 10;		// error
	n + 2 * 3 = 10; // error

	(n = 20) = 10;	// ok
	++n = 10;		// ok, n이 반환
	n++ = 10;		// error, 값이 반환
}
  • 표현식이 lvalue인지 rvalue인지 조사하는 방법
    decltype(expression)의 결과로 나오는 타입을 확인
    expression이 등호의 왼쪽에 올 수 있다면 lvalue reference 타입
#include <type_traits>

#define value_category(...)													\
			if ( std::is_lvalue_reference_v<decltype((__VA_ARGS__))> )		\
				std::cout << "lvalue" << std::endl;							\
			else if (std::is_rvalue_reference_v<decltype((__VA_ARGS__))>)	\
				std::cout << "rvalue(xvalue)" << std::endl;					\
			else                                                            \
				std::cout << "rvalue(prvalue)" << std::endl;

int main()
{
	int n = 10;

	value_category(n);		// lvalue
	value_category(n+2);	// prvalue
	value_category(++n);	// lvalue
	value_category(n++);	// prvalue
	value_category(10);		// prvalue
	value_category("AA");	// lvalue
}

Reference 규칙

int main()
{
	int n = 3;

	int& r1 = n; // ok
	int& r2 = 3; // error

	const int& r3 = n; // ok
	const int& r4 = 3; // ok
	
	// C++11
	int&& r5 = n; // error
	int&& r6 = 3; // rvalue를 상수성 없이 받을 수 있음
}

상수성 없이 rvalue를 가리키는 것이 중요한 이유는
move semantics와 perfect forwarding을 위해서!

  • reference의 종류
    lvalue reference(int&)
    rvalue reference(int&&)
  1. non-const lvalue reference는 lvalue만 가리킬 수 있음
  2. const lvalue reference는 lvalue와 rvalue를 모두 가리킬 수 있음
  3. rvalue reference는 rvalue만 가리킬 수 있음

reference와 overloading

  • overloading 규칙
    값 타입과 참조 타입은 오버로딩 불가능
    참조 타입끼리는 오버로딩 가능
class X{};

//void foo(X  x)
{ std::cout << "X" << std::endl;}

// out parameter, 객체를 수정하겠다는 의미
void foo(X& x) // lvalue만 받을 수 있음
{ std::cout << "X&" << std::endl;} // 1

// in parameter, 객체를 읽기만 하겠다는 의미
void foo(const X& x) // lvalue와 rvalue를 모두 받을 수 있음
{ std::cout << "const X&" << std::endl;} // 2

// move semantics를 사용하겠다는 의도
void foo(X&& x) // rvalue만 받을 수 있음
{ std::cout << "X&&" << std::endl;} // 3

// 문법적으로 만들 수 있지만 의미없음, const&에서 처리 가능 (현재 C++에서는 미사용)
void foo(const X&& x) // rvalue만 받을 수 있음
{ std::cout << "const X&&" << std::endl;}

int main()
{
	X x;
//	foo( x ); 	// lvalue
				// 1번 호출, 없으면 2번

	foo( X() );	// rvalue
				// 3번 호출, 없으면 2번
}
  • value 속성
int main()
{
	foo( X() ); // 3

	X&& rx = X();
	foo(rx); // 1
}

X()의 데이터 타입은 X며 temporory이므로 rvalue
rx의 데이터 타입은 X&&(rvalue reference)지만 이름이 있으므로 lvalue
foo(X&&)는 rvalue reference를 받는 것이 아닌 rvalue를 받겠다는 의미
그래서 foo(rx)는 foo(X&)를 호출

int main()
{
	// lvalue => rvalue 캐스팅
	foo(static_cast<X&&>(rx)); // 3
}

rx가 이미 X&& 타입이라 static_cast<X&&>(rx)는 같은 타입 캐스팅으로 보이지만,
타입 캐스팅에 '&&'이 붙으면 타입 캐스팅이 아닌 rvalue로 변환하는 캐스팅임


Reference Collapsing

  • 참조를 가리키는 참조 타입
    포인터를 가리키는 포인터처럼 참조를 가리키는 참조 변수를 직접 코드로 만들 순 없음
    하지만 type deduction(decltype) 과정에서 참조를 가리키는 참조 타입이 발생하면 reference collapsing 규칙에 따라 타입이 결정

  • reference collapsing 규칙
    Type& & -> Type&
    Type& && -> Type&
    Type&& & -> Type&
    Type&& && -> Type&&

int main()
{
    int n = 3;
    int&  lr = n; // lvalue reference
    int&& rr = 3; // rvalue reference

//	int& & ref2ref = lr; // 컴파일 에러
    
    decltype(lr)&  r1 = n; // int&  &   => int&
    decltype(lr)&& r2 = n; // int&  &&  => int&
    decltype(rr)&  r3 = n; // int&&  &  => int&
    decltype(rr)&& r4 = 3; // int&&  && => int&&
}
  • reference collapsing이 적용되는 경우
    typedef
    using
    decltype
    template
template<typename T> void foo(T&& arg) {}

int main()
{
    int n = 10;

    typedef int& LREF;
    LREF&& r1 = n; // int& && => int&

    using RREF = int&&;
    RREF&& r2 = 10; // int&& && => int&&

    decltype(r2)&& r3 = 10; // int&& && => int&&

    foo<int&>( n ); // foo( int& && arg )
                    // -> foo( int& arg ) 함수 생성
}

template의 T&&: forwarding(universal) reference 개념

profile
주니어 언리얼 프로그래머

0개의 댓글