Program Language/C & C++

[C++] rvalue 참조

야곰야곰+책벌레 2021. 12. 29. 15:00
728x90
반응형

하나 이상의 참조 종류를 갖는 기본 아이디어는 객체의 다양한 사용을 지원하는 것이다.

  • non const lvalue 참조는 참조 사용자가 참조할 수 있는 객체를 참조한다.
  • const value 참조는 상수를 참조하며, 이는 참조의 사용자다.
  • rvalue 참조는 참조 사용자가 할 수 있는 임시 객체를 참조한다. 일반적으로 객체가 다시는 사용되지 않을 것이라고 가정하고 수정한다.

참조가 임시를 참조하는지 알고 싶을 때가 있다. 만약 그렇다면 값 비싼 복사 작업을 저렴한 이동 작업으로 바꿀 수 있기 때문이다. 잠재적으로 엄청난 양의 정보를 가리키는 small descriptor로 표현되는 객체(문자열 혹은 목록)는 소스가 다시 사용되지 않을 것이라는 것을 안다면 간단하고 저렴하게 이동할 수 있다. 전형적인 예는 반환된 지역 변수가 다시는 사용되지 않을 것임을 컴파일러가 알고 있는 반환 값이다. 

rvalue 참조는 rvalue에 바인딩할 수 있지만 lvalue에는 바인딩할 수 없다. rvalue 참조는 다음과 같다. lvalue 참조와 정확히 반대다.

string var {"Cambridge"};
string f();
string& r1 {var}; // lvalue reference, bind r1 to var (an lvalue)
string& r2 {f()}; // lvalue reference, error : f() is an rvalue
string& r3 {"Princeton"}; // lvalue reference, error : cannot bind to temporar y
string&& rr1 {f()}; // rvalue reference, fine: bind rr1 to rvalue (a temporar y)
string&& rr2 {var}; // rvalue reference, error : var is an lvalue
string&& rr3 {"Oxford"}; // rr3 refers to a temporar y holding "Oxford"
const string cr1& {"Harvard"}; // OK: make temporar y and bind to cr1

&& 선언자 연산자는 "rvalue 참조"를 의미한다. const rvalue 참조는 사용하지 않는다. rvalue 참조를 사용하여 얻을 수 있는 대부분의 이점은 참조하는 개체에 쓰는 것과 관련이 있다. const lvalue 참조와 rvalue 참조는 모두 rvalue에 바인딩할 수 있다. 그러나 목적은 근본적으로 다른다.

  • rvalue 참조를 사용하여 사본이 필요하지 않은 항목을 최적화하기 위해 'destructive read'를 구현한다.
  • 인수 수정을 방지하기 위해 const lvalue 참조를 사용한다.

rvalue 참조가 참조하는 객체는 lvalue 참조 또는 일반 변수 이름이 참조하는 객체와 정확히 동일하게 액세스 된다.

string f(string&& s)
{
	if (s.size())
		s[0] = toupper(s[0]);
	return s;
}

때때로 프로그래머는 컴파일러가 사용하지 않더라도 객체가 다시 사용되지 않을 것임을 알고 있다.

template<class T>
swap(T& a, T& b) // "old-style swap"
{
	T tmp {a};// now we have two copies of a
	a = b; // now we have two copies of b
	b = tmp; // now we have two copies of tmp (aka a)
}

T가 문자열 및 벡터와 같은 요소를 복사하는 데 비용이 많이 들 수 있는 유형인 경우 이 swap()은 비용이 많이 드는 작업이 된다. 흥미로운 점은 사본을 전혀 원하지 않을 때다. 단지 b, tmp의 값을 옮기고 싶다. 컴파일러에 다음과 같이 말할 수 있다.

template<class T>
void swap(T& a, T& b) // "perfect swap" (almost)
{
	T tmp {static_cast<T&&>(a)}; // the initialization may write to a
	a = static_cast<T&&>(b); // the assignment may write to b
	b = static_cast<T&&>(tmp); // the assignment may write to tmp
}

static_cast <T&&>(x)의 결과 값은 x에 대한 T&& 유형의 rvalue다. rvalue에 최적화된 작업은 이제 x에 대한 최적화를 사용할 수 있다. 특히 유형 T에 이동 생성자 또는 이동 할당이 있는 경우 사용된다. vector의 경우를 보자.

template<class T> class vector {
// ...
	vector(const vector& r); // copy constr uctor (copy r’s representation)
	vector(vector&& r); // move constr uctor ("steal" representation from r)
};

vector<string> s;
vector<string> s2 {s}; // s is an lvalue, so use copy constr uctor
vector<string> s3 {s+"tail"); // s+"tail" is an rvalue so pick move constr uctor

swap()에서 static_cast를 사용하는 것은 약간 장황하고 오타가 발생하기 쉬우므로 표준 라이브러리는 move() 함수를 제공한다. move(x)는 static_cast <X&&>(x)를 의미한다. 여기서 X는 x의 유형이다. 이를 감안할 때 swap()의 정의를 약간 정리할 수 있다.

template<class T>
void swap(T& a, T& b) // "perfect swap" (almost)
{
	T tmp {move(a)}; // move from a
	a = move(b); // move from b
	b = move(tmp); // move from tmp
}

원래의 swap()과 달리 이 새로운 버전은 복사본을 만들 필요가 없다. 가능할 때마다 이동 작업을 사용한다. move(x)는 x를 움직이지 않기 때문에 (단순히 x에 대한 rvalue 참조를 생성함) move()가 rval()이라고 불렸다면 더 좋았겠지만 지금은 move()가 수년 동안 사용되고 있다. 이 swap()은 lvalue만 교환할 것이 때문에 '거의 완벽'하다고 생각할 수 있다.

void f(vector<int>& v)
{
	swap(v,vector<int>{1,2,3}); // replace v’s elements with 1,2,3
	// ...
}

컨테이너의 내용을 일종의 기본값으로 바꾸는 것은 드문 일은 아니지만 이 특정 swap()은 그렇게 할 수 없다. 해결 방법은 두 가지 오버로드로 확장하는 것이다.

template<class T> void swap(T&& a, T& b);
template<class T> void swap(T& a, T&& b

예제는 그 마지막 버전인 swap()에 의해 처리될 것이다. 표준 라이브러리는 vector, string 등에 대해 shrink_to_fit() 및 clear()를 정의하여 다른 접근 방식을 취한다. swap()에 대한 rvalue인수의 가장 일반적인 경우를 처리한다.

void f(string& s, vector<int>& v)
{
	s.shrink_to_fit(); // make s.capacity()==s.size()
	swap(s,string{s}); // make s.capacity()==s.size()
	v.clear(); // make v empty
	swap(v.vector<int>{}); // make v empty
	v = {}; // make v empty
}

rvalue 참조를 사용하여 완벽한 전달을 제공할 수도 있다. 모든 표준 라이브러리 컨테이너 이동 생성자와 이동 할당을 제공한다. 또한 insert() 및 push_back()과 같은 새 요소를 삽입하는 작업에는 rvalue 참조를 사용하는 버전이 있다.

728x90
반응형

'Program Language > C & C++' 카테고리의 다른 글

[C++] 포인터와 참조  (1) 2021.12.29
[C++] 참조에 대한 참조  (0) 2021.12.29
[C++] lvalue 참조  (0) 2021.12.29
[C++] 참조(references)  (0) 2021.12.29
[C++] pointer & 소유권  (0) 2021.12.29