[Effective Modern C++] R-value reference와 Universal reference
R-value reference는 표기할 때 &&로 표기합니다.
형식 T에 대한 R-value reference는 T&&이 되는 것 입니다.
그러나 T&&이 항상 R-value reference가 되지는 않습니다.
이번 글에서는 그러한 예외에 대하여 살펴보도록 하겠습니다.
1. Universal reference
void function_1(MyClass&& param);
MyClass&& var_1 = MyClass();
auto&& var_2 = var_1;
template<typename T>
void function_2(std::vector<T>&& param);
template<typename T>
void function_3(T&& param);
위 다섯가지 코드에서, 각각의 &&가 의미하는 것은 다음과 같습니다.
function_1(MyClass&& param) | R-value refenrece |
MyClass&& var_1 | R-value reference |
auto&& var_2 | Not R-value reference |
function_2(std::vector<T>&& param) | R-value reference |
function_3(T&& param) | Not R-value reference |
&&에는 두 가지 의미가 있습니다.
첫 번째는 R-value reference입니다. R-value에만 묶이며, 이동의 원본이 되는 객체를 지정합니다.
두 번째는 R-value reference 혹은 L-value reference입니다. 때에 따라서 R-value, L-value, 더 나아가서 const 혹은 volatile객체에도 묶일 수 있습니다.
이러한 참조를 보편 참조(Universal reference)라고 합니다.
2. Universal reference 판별하기
Universal reference는 두 가지 문맥에서 나타납니다.
template<typename T>
void function(T&& param);
auto&& var_2 = var_1;
첫 번째는 함수 템플릿 매개변수, 두 번째는 auto 선언입니다.
이 두 문맥의 공통점은 형식 연역이 일어난다는 것 입니다.
형식 연역이 일어나지 않는 문맥에서의 &&는 R-value reference 입니다.
3. Universal reference의 특징
Universal reference는 참조이므로, 반드시 초기화 해야 합니다.
초기화 할 때의 초기치에 따라, Universal reference의 R-value, L-value 여부가 결정됩니다.
초기치가 R-value이면 R-value reference, L-value이면 L-value reference가 되는 것 입니다.
tmeplate<typename T>
void function(T&& param);
MyClass c;
function(c);
function(std::move(c));
위 문맥에서, function(c)는 L-value reference(MyClass&)가 되고, function(std::move(c))는 R-value refenrece(MyClass&&)가 되는 것 입니다.
Universal reference는 참조 선언의 형태 또한 정확해야 합니다.
구체적으로, T에 대하여 반드시 'T&&'의 형태만 가능합니다.
template<typename T>
void function_2(std::vector<T>&& param);
std::vector<int> v;
function_2(v); // Error
예를 들어, 위와 같은 선언은 Universal이 아닌 R-value reference입니다.
따라서 L-value를 묶을 수 없는 것 입니다.
위와 같이 형식이 반드시 'T&&'의 형태이어야 하기 때문에, const등의 한정사 또한 붙일 수 없습니다.
'T&&'의 형태이더라도, 형식 연역이 일어나지 않는 경우 Universal reference가 되지 않습니다.
template<class T, class Allocatoe = callocator<T>>
class vector {
public:
void push_back(T&& x);
...
};
위 코드는 std::vector<T>의 일부입니다.
위 컨테이너의 push_back함수는 매개변수가 T&&지만, Universal reference가 아닙니다.
std::vector 컨테이너가 생성될 때 이미 T가 결정되기 때문에, push_back에서는 형식 연역이 일어나지 않기 때문입니다.
반면, push_back과 유사한 함수인 emplace_back은 형식 연역을 사용합니다.
template<class T, class Allocator = allocator<T>>
class vector {
public:
template<class... Args>
void emplace_back(Args&&... args);
...
};
위의 Args는 std::vector의 T와 독립적입니다.
따라서 emplace_back이 호출될 때 마다 연역되어야 하고, Universal reference가 됩니다.
auto 또한 마찬가지로, auto&&형식의 참조는 Universal reference가 됩니다.
이 형식은 C++14의 람다에서 자주 나타나는데, auto&& 매개변수를 선언할 수 있기 때문입니다.
auto timeFuncInvocation =
[](auto&& func, auto&&... params) {
// Timer start
std::forward<decltype(func)>(func)(
std::forward<decltype(params)>(params)...
);
// Timer stop
};
위와 같은 시간을 기록하는 C++14의 람다에서, func와 params는 Universal reference가 됩니다.
R-value reference와 Universal reference는 &&가 있다는 점에서 그 형태가 유사합니다.
하지만 사용처가 다르기 때문에 숙지할 필요가 있습니다.
L-value에서 R-value, 거기서 다시 Universal refenrece로 개념을 확장하는 느낌으로 접근한다면 어렵지 않을 것이라 생각합니다.
이는 또한 코드 더 정확하게 읽는데 도움이 될 것입니다.
감사합니다.