하나 이상의 참조 종류를 갖는 기본 아이디어는 객체의 다양한 사용을 지원하는 것이다.
- 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 참조를 사용하는 버전이 있다.
'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 |