Perfect forwarding
퍼펙트 포워딩에 알아보기전에 타입 추론에 대한 내용부터 알아야한다.
template<typename T>
void PrintVar(T a)
{
std::cout << typeid(a).name() << std::endl;
std::cout << a << std::endl;
}
int main()
{
int a = 1;
PrintVar(a);
double b = 1.1;
PrintVar<int>(b);
}
위와 같은 템플릿 함수를 정의했을 때 PrintVar(a)는 타입 추론(Type deduction)에 의해 angle bracket(<>)을 쓰지 않아도 컴파일러가 알아서 컴파일을 해준다. 그런데 b를 보면 컴파일러가 알아서 지정해주는 것보다 직접 <>로 타입을 지정해주는 게 우선순위가 더 높다는 것을 알 수 있다.
foward reference (universial reference)
template<typename T>
void PrintVar(T& a)
{
std::cout << typeid(a).name() << std::endl;
std::cout << a << std::endl;
}
템플릿도 일반적인 Reference로 받는 것이 가능하다. RValue Reference를 받는 것 또한 가능한데 여기서 템플릿의 난해함이 발생하게 된다. 일반적인 함수에서 RValue Reference를 받는 함수에 LValue를 넘겨주게 되면 컴파일 에러가 발생한다.
이를 템플릿에서도 똑같이 만들어주고 호출할 수가 있는데,
template<typename T>
void PrintVar(T&& a)
{
std::cout << typeid(a).name() << std::endl;
std::cout << a << std::endl;
}
int main()
{
PrintVar<int>(a); // <>로 지정해주면 컴파일 에러 발생
PrintVar(a); // 타입 추론을 하라고 하면 빌드가 된다.
}
이렇듯 템플릿에서는 && 2개가 붙어 있으면 이는 RValue Reference가 아니라 Forward Reference 혹은 Universial Reference라고 부른다. 이는 LValue Reference 가 되기도하고 RValue Reference 가 되기도 하기 때문이다.
Compiler Explorer로 확인해보면
함수 PrintVar의 타입 T는 int& 이고 argument 역시 LValue Ref인 int&가 된다.
- 타입 T : int&
- Argument : int&
그런데 RValue로 넘겨주면 타입 T는 순수한 int이고 argument는 RValue의 Reference인 int&&가 된다.
- 타입 T : int
- Argument : int&&
즉, Forward Reference로 받았기 때문에 LValue Ref도 받을 수 있고 RValue Ref도 받을 수 있게 컴파일이 된 것이다.
std::forward
std::forward는 LValue와 RValue의 특성을 살려주는 역할을 한다.
Example Code
template<typename T>
void PrintVar(T&& a)
{
std::string localVar{ std::move(a) }; // a를 localVar에서 사용하고 싶은 경우
std::cout << a << std::endl;
}
int main()
{
std::string a = "abcd";
PrintVar(a);
PrintVar(std::move(a));
}
a가 RValue Reference로 넘어오면 효율적인 사용을 위해 std::move()를 사용해야하는데 그렇게 되면 소유권이 localVar로 넘어가기 때문에 문제가 생길 수 있다.
즉,
- PrintVar(a)의 경우
void PrintVar(string& a);
T = string& (string&) 와 같이 넘어가므로 std::move가 되면 a는 소유권을 잃어버린다.
- PrintVar(std::move(a))의 경우
void PrintVar(string&& a);
T = string (string&&)으로 넘어가는데 위에서 a가 소유권을 잃었으므로 아무것도 가르키지 않는다.
template<typename T>
void PrintVar(T&& a)
{
std::string localVar{ std::forward<T>(a) }; // a를 localVar에서 사용하고 싶은 경우
std::cout << a << std::endl;
}
그런데 여기서 std::foward를 사용하면 Reference의 특성을 그대로 살려준다. 즉, LValue Reference로 넘어오면 LValue Reference로 받아주어 a는 복사가 일어나게 되고, RValue Reference로 넘어오면 RValue Reference를 받아주어 a는 move가 일어난다.
'C++ > Modern C++' 카테고리의 다른 글
[C++] 가상함수 원리 (0) | 2022.10.24 |
---|---|
[C++] attributes(속성) (0) | 2022.10.13 |
[C++] constexpr (0) | 2022.10.05 |
스마트 포인터 문제 - 순환 참조 (0) | 2022.10.03 |
[C++20] std::span (0) | 2022.10.01 |