C++/Effective Modern C++

[Effective Modern C++] R-value reference와 Universal reference

ruru14 2022. 7. 13. 22:24

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로 개념을 확장하는 느낌으로 접근한다면 어렵지 않을 것이라 생각합니다.

이는 또한 코드 더 정확하게 읽는데 도움이 될 것입니다.

 

감사합니다.