ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 3. 완벽 전달(Perfect Forwarding)
    Modern C++/Move Semantics 2020. 6. 29. 23:45

    완벽 전달은 임의의 인수를 받아서 이를 다른 함수로 전달할 때 대상 함수에 전달 함수가 받은 것과 동일한 인수를 받아들 일 수 있게 한다. 완벽 전달을 이해하기 위해서 보편참조에 대해 살펴보자. 

     

    오른쪽 참조와 보편 참조의 구분

    타입 T 대한 오른값 참조는 T&& 표기하고 이는 오직 오른값에 바인딩되어 해당 객체가 이동이   있다는  표현한다. 

    void f(int&& a); // int&& 오른값 참조

    int&& a = 10; //int&& 오른값 참조

    T&&  형식 추론(Type Deduction) 엮이면 해당 표기는 보편 참조를 나타낸다. 이는 오른값 참조와 왼값 참조 모두에 바인드   있다. 

    보편 참조는 형식 추론이 일어나는 템플릿 함수의 매개변수나 auto 선언에서   있다. 

    auto&& b = a; //auto&&  보편 참조

    template <typename T>

    void f(T&& param); // T&& 보편참조

    따라서 형식 추론이 발생하지 않는 곳에서의 타입 T&& 오른값 참조이고 반대이면 보편참조가 된다. 

    template <typename T>

    std::queue<T> convert_queue(std::vector<T>&& param) //오른값 참조

    위의 템플릿 함수는 파라미터 std::vector<T>&&  대한 형식 추론이 발생하지 않으므로 오른값 참조이다. 

     

     

    #include <iostream>
    #include <boost/type_index.hpp>
    
    using namespace std;
    using namespace boost;
     
    template <typename T>
    void translate_type(T&& arg) {
        using boost::typeindex::type_id_with_cvr;
        std::cout << "T= " << type_id_with_cvr<T>().pretty_name() << std::endl;
        std::cout << "ParamType = " << type_id_with_cvr<T&&>().pretty_name() << std::endl;
    }
     
    int main() {
    	int x = 3;
    	const int cx = x;
    	const int& rx = x;
    	translate_type(x); // x는 왼값 T = int& , ParmType = int&
    	translate_type(cx); // cx는 왼값 T = const int&, ParamType = const int&
    	translate_type(rx); // rx는 왼값 T = const int&, ParamType = const int&
    	translate_type(5); //5는 오른 값 T = int, ParamType = int&&
    	translate_type(std::move(x)); //std::move(x) 오른 값 참조 T = int, ParamType = int&&
    	return 0;
    }

     

    보편 참조는 참조에 해당되므로 반드시 초기화 되어야 하고, 초기화  오른값 참조인지 왼값 참조인지 결정된다. 

    보편 참조가 초기화   왼값으로 초기화 되면 왼값 참조, 오른값이면 오른값 참조가 된다. 

     

    다시 한번 강조하자면 타입 T&&  형식 추론이 발생한다면 해당 참조는 보편 참조가 되고, 보편 참조는 왼값으로 초기화되면 왼값 참조, 오른쪽 값으로 초기화되면 오른쪽 참조가 된다.

     

    std::forward

    std::forward는 전달 함수에 들어온 인수의 타입을 대상함수에 정확하게 전달하기 위해서 사용된다. 

    std::forward
    template <class T>
    constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;
    template <class T>
    constexpr T&& forward(std::remove_reference_t<T>&& t) noexcept;
    Return Value
    static_cast<T&&>(t)
    void func_destination(int && a) {
        std::cout << "parameter is rvalue reference" << std::end;
    }

    void func_destination(const int& a) {
        std::cout << "parameter is lvalue reference" << std::endl;
    }

    template <typename T>
    void func_source(T&& param) {
        func_destination(param);
    }

    int x = 10;
    func_source(x); //  func_source(int&)
    func_source(10); // func_source(int&&)

    출력 
    parameter is lvalue reference
    parameter is lvalue reference

    func_source() 함수가 매개 변수가 보편참조로 해당 매개변수로 들어온 형식에 따라 재정의된 두 함수 func_destination 를호출하기 위해 위와 같이 코드를 작성한다고 가정하자. func_source(x)는 왼값 참조이므로 fun_destination(const int& a) 가 호출 되고, func_source(10)은 오른값 이므로 func_destination(int&& a)가 호출 될거라 생각하지만, 두 함수 호출은 모두 func_destination(const int& a)를 호출한다.

    이유는 간단하게 func_source 의 param 자체가 왼값이므로 두 함수 모두 왼값 참조에 해당하는  함수를 호출하게 된다.

    이처럼 매개 변수를 전달할 함수가 매개 변수의 타입을 완벽하게 전달하기 위해서 std::forward()가 사용된다.

    template <typename T>
    void func_source(T&& param) {
        func_destination(std::forward<T>(param));
    }

    std::forward 파라미터가 왼값인 경우 왼값 참조를 오른값인 경우 오른값 참조로 static_cast 된다. 

    std::forward 가 실제 완벽 전달을 수행하지 않고 static_cast 된다는 점은 std::move와 닮았다. 차이점은 std::move는 왼값, 오른값에 관계없이 오른쪽 참조로 static_cast 되지만 std::forward는 파라미터 타입에 따라 왼값 참조나 오른값 참조로 static_cast 된다는 점이다. 

     

     

    'Modern C++ > Move Semantics' 카테고리의 다른 글

    5. C++11 객체 생성 규칙 Rule of Five  (0) 2020.07.01
    4. 이동가능 객체(Movable Type)  (0) 2020.06.30
    2. std::move  (0) 2020.06.29
    1. 이동 의미론(Move Semantics)  (0) 2020.06.29

    댓글

Designed by Tistory.