프로그래밍/C/C++

C++ Perfect forwarding (std::forward 의 역할)

nanze 2022. 1. 8. 23:59
반응형

이전 정리글은 

2022.01.07 - [프로그래밍/C/C++] - C++ Universal reference & Reference Collapsing Rules

 

C++ Universal reference & Reference Collapsing Rules

이전 정리글은 2022.01.07 - [프로그래밍/C/C++] - C++ rvalue reference 우측값 참조 속성 C++ rvalue reference 우측값 참조 속성 이전 정리글 2022.01.06 - [프로그래밍/C/C++] - C++ rvalue reference (우측값..

nanze.tistory.com

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));
}

반응형