이전 정리글은
2022.01.07 - [프로그래밍/C/C++] - C++ Universal reference & Reference Collapsing Rules
Perfect forwarding
이번 정리는 Perfect forwarding 에 대한 정리를 해보자. 직역하면 완벽한 전달 ? 쯤 되는 것 같다. 우선 아래 코드를 보자.
class cls1
{
public:
cls1(int& a, int& b){}
};
class cls2
{
public:
cls2(const int& a, int& b){}
};
class cls3
{
public:
cls3(int& a, const int& b){}
};
class cls4
{
public:
cls4(const int& a, const int& b){}
};
template <typename T, typename arg1, typename arg2>
T* getInstance(arg1& a1, arg2& a2){
return new T(a1, a2);
}
int main()
{
int arg1 = 0, arg2 = 1;
cls1* c1 = getInstance<cls1>(arg1, arg2);
cls4* c4 = getInstance<cls4>(0, 1); // 컴파일 에러 발생.
return 0;
}
위 코드를 보면 getInstance<cls4>(0,1) 호출 부분에서 컴파일 에러가 발생한다. 이유는 const 가 아닌 좌측값 참조 타입은 우측값(rvalue)을 받을 수 없기 때문이다. 이 문제를 해결하기 위해서는 c++11 이전에는 모든 경우의 template 함수를 작성해야만 했다. 아래와 같이 말이다.
template <typename T, typename arg1, typename arg2>
T* getInstance(arg1& a1, arg2& a2){
return new T(a1, a2);
}
template <typename T, typename arg1, typename arg2>
T* getInstance(const arg1& a1, arg2& a2){
return new T(a1, a2);
}
template <typename T, typename arg1, typename arg2>
T* getInstance(arg1& a1, const arg2& a2){
return new T(a1, a2);
}
template <typename T, typename arg1, typename arg2>
T* getInstance(const arg1& a1, const arg2& a2){
return new T(a1, a2);
}
만약 같은 조건에서 인자가 늘어나는 경우에는 더 많은 템플릿 함수를 정의해야 한다. 이문제는 앞서 정리한 우측값 참조 표현을 쓰는 universal reference 에 의해서 해결된다. 아래 코드를 보자.
template <typename T, typename arg1, typename arg2>
T* getInstance(arg1&& a1, arg2&& a2){
return new T(a1, a2);
}
위와 같이 선언하면 좌측값이든 우측값이든 모두 수용할 수 있다. 그런데 문제가 있다. a1 과 a2 자체는 함수 내부에서 좌측값(lvalue) 이기 때문에 인자로 넘어올 때 좌측값인지 우측값인지 판단이 필요한 것이다. 왜냐하면 함수 내부에서 다른 함수의 인자로 넘어갈 때도 본래의 성질을 유지해주어야 정확하고 의도된 함수 호출이 될 수 있기 때문이다. 이렇게 값의 전달이 성격을 잃지않고 전달되는 것을 Perpect forwarding 이라고 하면 해당 부분은 우측값 참조(rvalue reference)로 강제 타입 캐스팅함으로써 해결된다.
template <typename T, typename arg1, typename arg2>
T* getInstance(arg1&& a1, arg2&& a2){
return new T(static_cast<arg1&&>(a1), static_cast<arg2&&>(a2));
}
a1으로 좌측값이 전달되었을 경우 a1의 타입은 좌측값 참조(lvalue reference) 이며 이것을 우측값 참조(rvalue reference)로 강제 캐스팅하면 reference collapsing rule 에 의해서 & + && => & 좌측값 참조가 된다. 처음 인자로 넘어온 대로의 성질을 잘 유지하고 있다.
a1으로 우측값이 전달되었을 경우 a1의 타입은 우측값 참조(rvalue reference) 이며 이것을 우측값 참조(rvalue reference)로 강제 캐스팅하면 reference collapsing rule 에 의해서 && + && => && 우측값 참조가 된다. a1 좌측값일 경우와 마찬가지로 처음 인자로 넘어온 대로의 성질을 잘 유지하고 있다. 이렇게 템플릿 함수 인자로 넘어온 값의 성질을 유지한 채로 내부 함수에서 전달되어야 정확한 수행이 일어난다.
위의 강제 캐스팅 부분을 하는 std 함수가 바로 forward() 로 템플릿 함수로 넘어온 인자에 대해서 좌측값은 좌측값으로 우측값은 우측값으로 형변환 해준다. 위 코드는 아래와 같은 것이다.
template <typename T, typename arg1, typename arg2>
T* getInstance(arg1&& a1, arg2&& a2){
return new T(std::forward<arg1>(a1), std::forward<arg2>(a2));
}
'프로그래밍 > C/C++' 카테고리의 다른 글
C++ Template and Integral_constant (0) | 2022.01.12 |
---|---|
C++ template 과 type 추론 (0) | 2022.01.11 |
C++ Universal reference & Reference Collapsing Rules (0) | 2022.01.07 |
C++ rvalue reference 우측값 참조 속성 (0) | 2022.01.07 |
C++ rvalue reference (우측값 참조) move semantics (0) | 2022.01.06 |