M.2 R-value references

주홍영·2022년 3월 22일
0

Learncpp.com

목록 보기
192/199

https://www.learncpp.com/cpp-tutorial/rvalue-references/

챕터 1에서 우리는 l-value와 r-value에 대해서 이야기 했었다
그리고 이에 대해서 걱정할 것이 없다고 말했다
c++11 전까지는 나름 유효한 조언이었으나 c++11의 move semantics를 이해하기 위해서
이 주제에 대해서 재조명해야 할 필요가 있다

L-values and r-values

"value"라는 이름에도 불구하고 이들은 value에 특성이 아닌 expression의 특성에 대해서 이야기 하고 있다.

c++의 모든 expression은 두가지 특성을 가지고 있다
하나는 타입 (타입 checking에 쓰이는)
다른 하나는 value category (expression의 결과를 다음에 할당할 수 있는지 여부와 같은 특정 종류의 구문 검사에 사용된다)
c++03 이전에는 l-value와 r-value 두가지 value cateogyr만 이용 가능했다

정확히 어떤 expression이 l-value이고 r-value인지는 꽤나 목잡하다.
따라서 간단하게 우리의 목적을 만족하는 범위내에서 살펴본다

l-value는 function 혹은 object로 생각하는 것이 가장 간단하다. 모든 l-value는 assigned memory를 address를 가지고 있다

원래 정의되기론 l-value는 assignment expression의 left-hand side에 적합한 value라고 했지만 const 키워드가 추가되고나서 l-value는 두 가지 sub-categories로 분리되었다
modifiable l-value, 그리고 non-modifiable l-value(const)

r-value는 간단하게 l-value가 아닌 모든 것이라고 생각하면 된다.
예를들어 literal (e.g. 5), temporary values (e.g. x+1), 그리고 anonymous object (e.g. Class(5,2))와 같은 것들이 있다. r-value는 일반적으로 그들의 value를 평가받고 expression scope를 지니고 있고 (scope를 벗어나면 die), 값을 할당받지 못한다.

Move semantics를 지원하기 위해 c++11에서는 3가지 새로운 카테고리를 소개했다
pr-values, x-values, and gl-values. 일단 우리는 이를 생략한다

L-value reference

c++11 이전에는 오직 "reference"라는 타입의 reference만 존재했다
그러나 c++11에서는 l-value reference라고 불리운다
L-value reference는 오직 modifiable l-value로만 initialized가 가능하다

L-value reference to const object는 l-value와 r-value로 initialized 가능하다
그러나 이 value는 수정이 불가능하다

const object에 대한 L-vlaue reference는 argument의 copy를 만들지 않고도 모든 유형의 argument(l-value 또는 r-value)를 함수에 전달할 수 있기 때문에 특히 유용합니다.

R-value references

c++11에서는 새로운 타입의 reference가 추가되었다. 이는 r-value reference라고 불리운다
r-value reference는 오직 r-value롤만 initialized 될 수 있도록 설계되었다
l-value reference가 single ampersand를 사용하는 반면에
r-value reference는 double ampersand로 생성된다

int x{ 5 };
int &lref{ x }; // l-value reference initialized with l-value x
int &&rref{ 5 }; // r-value reference initialized with r-value 5

R-value reference는 l-value로 initialized될 수 없다

R-value reference는 두가지 유용한 특성을 가지고 있다
첫째, rv ref는 initialized에 사용한 object의 수명을 r-value ref의 수명으로 연장시킨다
둘째, non-const r-value reference는 r-value를 수정할 수 있게 허용해준다

예시를 살펴보자

#include <iostream>

class Fraction
{
private:
	int m_numerator;
	int m_denominator;

public:
	Fraction(int numerator = 0, int denominator = 1) :
		m_numerator{ numerator }, m_denominator{ denominator }
	{
	}

	friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
	{
		out << f1.m_numerator << '/' << f1.m_denominator;
		return out;
	}
};

int main()
{
	auto &&rref{ Fraction{ 3, 5 } }; // r-value reference to temporary Fraction

    // f1 of operator<< binds to the temporary, no copies are created.
    std::cout << rref << '\n';

	return 0;
} // rref (and the temporary Fraction) goes out of scope here

위 프로그램의 출력은 다음과 같다

3/5

anonymous object이므로 일반적으로 expression이 끝나면 scope를 벗어나게 된다
그러나 우리가 r-value ref로 initialize 했기 때문에 block이 끝나기 전까지 수명이 연장됐다
그리고 우리는 이를 print에 사용할 수 있다

다음 예시를 살펴보자

#include <iostream>

int main()
{
    int &&rref{ 5 }; // because we're initializing an r-value reference with a literal, a temporary with value 5 is created here
    rref = 10;
    std::cout << rref << '\n';

    return 0;
}

위 프로그램의 출력은 다음과 같다

10

literal vlaue를 수정하는게 이상해보일 수는 있지만
위와 같이 r-value ref는 literal로 initialize 됐음에도 불구하고 수정이 가능하다

R-value ref는 앞선 사례와 같이 잘 사용되지는 않는다

R-value references as function parameters

R-value ref는 function parameter로 자주 사용된다
이는 l-value, r-value에 각기 다른 function을 overload할 때 유용하다

void fun(const int &lref) // l-value arguments will select this function
{
	std::cout << "l-value reference to const\n";
}

void fun(int &&rref) // r-value arguments will select this function
{
	std::cout << "r-value reference\n";
}

int main()
{
	int x{ 5 };
	fun(x); // l-value argument calls l-value version of function
	fun(5); // r-value argument calls r-value version of function

	return 0;
}

이는 다음과 같은 출력을 준다

l-value reference to const
r-value reference

보시다시피 l-value를 pass하면 overload function을 l-value ref로 이를 매칭하고
r-value의 경우 r-value ref로 매칭한다

언제 우리는 이렇게 되기를 원할까? 자세한 내용은 다음 레슨에서 살펴보자
이는 말할 필요도 없이 move semantics에서 중요한 부분이다

한가지 흥미로운 예시가 있다

int &&ref{ 5 };
fun(ref);

이렇게 되면 ref는 r-value ref지만 l-value버젼의 fun function을 호출한다
비록 r-value ref지만 ref 스스로는 l-value이기 때문이다
이렇게 보면 혼동스럽다 따라서 다음과 같이 이해해보자
Named-object는 l-value이고 Anonymous object는 r-value이다
type은 object가 r-value인지 l-value인지 결정하지 않는다

Returning an r-value reference

우리가 l-value ref로 return 값을 설정하지 않았듯이
r-value ref로도 우리는 return 값을 설정하지 않는다
r-value ref도 마찬가지로 scope를 벗어나는 순간 수명이 끝나기 때문이다

profile
청룡동거주민

0개의 댓글