perfect forwarding
[C++] Template Type Deduction (템플릿 타입 추론) : Perfect Forwarding
Template으로 만들어진 함수를 사용하기 위해서는 함수명 뒤에 Argument Type을 명시를 해주어야 했다. 하지만 Argument Type을 명시하지 않아도 컴파일러가 Argument를 보로 Deduction (추론)을 하여 해당 타입의 함수를 만들어 준다.
Template Type Deduction (템플릿 타입 추론)
Template으로 구선된 함수는 타입을 추론할 수 있다. 아래 코드는 타입이 명시됐을 때와 안 됐을 때의 예를 보여주는 코드이다.
#include <iostream>
template<typename T>
void printVar(T a)
{
std::cout << typeid(a).name() << std::endl;
std::cout << a << std::endl;
}
int main()
{
int a = 100;
printVar<int>(a); //type 명시
printVar(a); //type 미명시
return 0;
}
main함수에서 Type을 명시하여 Template함수를 호출하는 것과 Type을 명시하지 않고 Template함수를 호출하였다. 출력 화면은 아래와 같다.
출력 콘솔에서 확인을 하게 되면 두 호출 모두 int 타입의 함수가 만들어지고 Argument로 넘어간 값도 출력이 잘 되었다. 이와 같이 Template 함수는 Argument를 보고 Type을 Deduction (추론)을 하게 된다. 일만 integer나 string클래스에 대해서는 다 문제없이 잘 작동하는데 Parameter를 Reference로 받거나 R-Value로 받을 경우는 어떻게 되나 궁금할 수 있다.
Template 함수도 일반 함수와 같이 Reference Type 혹은 R-value Reference Type으로 받는 것이 가능하다. 먼저 Reference와 R-Value Reference의 일반 함수를 만들어 보면 아래와 같다.
#include <iostream>
template<typename T>
void printLRef(int& a)
{
std::cout << a << std::endl;
}
void printRRef(int&& a)
{
std::cout << a << std::endl;
}
int main()
{
int a = 100;
printLRef(a);
printRRef(a); //ERROR
return 0;
}
printLRef는 L-Value를 printRRef는 R-Vlaue를 받아주는데 main함수에서 printRRef(a);는 L-Value를 넘겨주기 때문에 컴파일 에러가 난다. 해당 에러를 고쳐주기 위해서는 std::move() 함수를 통해서 R-Vlaue로 변환을 한 후에 Argument로 넘겨줘야 한다.
Template 함수를 이용해 위와 같은 함수를 만든다고 가정을 해보자. Template을 사용하는 이유는 어떠한 Type을 Argument로 주어도 Compile-time에 해당 타입에 맞는 함수를 만들어 줘야 한다. Template 함수에서 Reference를 이용해서 Argument를 넘겨주는 방법은 아래와 같다.
#include <iostream>
template<typename T>
void printVar(T&& a)
{
std::cout << a << std::endl;
}
int main()
{
int a = 100;
printVar(a);
printVar(std::move(a));
return 0;
}
위 코드에서 Template함수의 Parameter는 T&& R-Value를 받게 되어있다. 하지만 main함수에서 L-Value를 Argument로 넘겨주었는데 에러 없이 컴파일이 문제없이 되었다. 이유는 Template에서 T&&는 R-Value Reference가 아니라 Forward Reference라고 부른다. 당연하게 이는 L-Value Reference가 되기도 하고 R-Value Reference가 되기도 한다.
더 자세히 위 내용을 이해하기 위해 Compiler Explorer를 이용해서 어셈블리 코드를 보자. 아래는 위 코드를 어셈블리 코드로 변환시킨 화면이다.
어셈블리 코드를 보게 되면 컴파일러가 함수의 Argument를 보고 맞는 타입의 함수를 만든 것을 확인할 수 있다. L-Value reference를 Argument로 받은 함수는 T가 int&이고 함수의 Parameter는 int&가 된다. R-Value Reference를 Argument로 받은 함수는 T가 int고 Parameter는 int&&가 된다.
R-Value Reference를 사용하는 이유 중 하나는 소유권을 뺏어오는 목적이 있다. 위와 같은 L-Value와 R-Value를 둘 다 넘겨줄 수 있는 Template 함수에서 소유권은 어떻게 취득하는지 알아보자.
Perfect Forwarding
#include <iostream>
template<typename T>
void printVar(T&& a)
{
std::string localVar{ std::forward<T>(a) };
std::cout << localVar << std::endl;
}
int main()
{
std::string str = "pretending";
printVar(str);
printVar(std::move(str));
return 0;
}
main 함수에서 string 객체인 str을 선언하고 template 함수의 Argument로 각각 L-Value Reference와 R-Value Reference로 호출하였다. Template 함수 내에서 localVar이라는 string 객체를 만들어주고 std::forward <T>(a);로 초기화를 해주었는데 이때 std::forward <T>(a);에서 만약 a가 L-Value Reference라면 그대로 L-Vlaue Reference로 놔두게 되고 R-Vlaue Reference라면 그래도 R-Value Reference로 놔두게 된다.
Ref.
https://www.youtube.com/watch?v=Ifo2RtSzqvQ&list=PLDV-cCQnUlIb2oezNpNTmxiiX_NibMrlO&index=3
'Modern C++' 카테고리의 다른 글
[C++] Class, Aliasing, Variable Template (여러 템플릿들) (0) | 2021.08.27 |
---|---|
[C++] Template Instantiation (템블릿 인스턴스화) (0) | 2021.08.27 |
[C++] Template Intro. (템플릿) : Function Template (함수 템플릿) (0) | 2021.08.26 |
[C++] Weak Pointer (위크 포인터) : Circular Reference (순환 참조) 해결 (0) | 2021.08.25 |
[C++] Shared Pointer (쉐어드 포인터) : Circular Reference (순환 참조) (0) | 2021.08.25 |