ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Modern C++] R-value reference와 Universal reference
    C++/Effective Modern C++ 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로 개념을 확장하는 느낌으로 접근한다면 어렵지 않을 것이라 생각합니다.

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

     

    감사합니다.

    댓글

Designed by Tistory.