-
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 referencefunc_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) (1) 2020.06.29