본문으로 바로가기

C++11) std::forward (perfect forwarding)

category C++/Modern 2019. 2. 2. 02:17

<개요>

 

std::move 이후, std::forward 와 완벽 전달 (perfect forwarding) 에 대해 작성합니다

 

<Forwarding Problem>

 

우리가 수 많은 class를 만들었다고 합시다.

그런데 그 class가 너무 많아 차라리 팩토리 함수를 하나 만들어서 생성해주면 편할 것 같군요

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
 
class A {
    A(int& _a) { }
};
class B {
    B(const int& _b) { }
};
 
template <typename T, typename U>
T* Factory(U& arg) {
    return new T(arg);
}
 
int main() {
    int i = 1;
    A* aa = Factory<A>(i);    //ok, Factory 템플릿은 lvalue인 i 를 받아 A를 만들어냅니다.
    B* bb = Factory<B>(1);    //error, Factory 템플릿은 U&형입니다. 따라서 상수인 rvalue 를 받을 수 없습니다.
}
cs

 

팩토리 함수는 Factory(U& arg) 형으로 선언돼있습니다. 상수인 1은 const int& 형이기때문에 올수가 없죠 (또는 int&&)

그래서 팩토리 함수를 하나 더 추가해 오버로딩 해주기로 했습니다.

 

1
2
3
4
template <typename T, typename U>
T* Factory(const U& arg) {
    return new T(arg);
}
cs
1
2
3
4
5
6
 
int main() {
    B* bb = Factory<B>(1);    //ok, 이제 Factory(const U& arg) 형을 가지고 만들어낼 수 있습니다.
}
cs

 

좋습니다. 이제 aa와 bb를 둘다 만들 수 있게 됐습니다. 이걸로 끝인걸까요?만약 인자가 2개인 class가 새로 추가된다면요?, 그 인자가 따로 const int& 형과 int& 형을 받는다면?새로운 데이터 타입이 추가되고 그것이 const T& 형 또는 T& 형 의 생성자라면?각각의 인자에 맞춰 Factory를 lvalue 형과 rvalue 형을 오버로딩 해줘야 할것이고, 한 클래스당 최소 2개의 팩토리 템플릿을 오버로딩 해줘야 할것입니다.

이를 막기위해 C++11 부턴 rvalue reference가 나왔습니다. 

 

 

1
2
3
4
5
6
7
8
9
10
 
template <typename T, typename U>
T* Factory(U&& arg) {
    return new T(arg);
}
int main() {
    int i = 1;
    A* aa = Factory<A>(i); //ok , collapsing 에 따라 U& arg 형으로 변환됩니다.
    B* bb = Factory<B>(1); //ok , collapsing 에 따라 U&& arg 형으로 변환됩니다.
}
cs

 

aa는 팩토리 템플릿에 들어가 U& &&arg 형으로 변할 것이고 이는 U& arg 형입니다.

bb는 팩토리 템플릿에 들어가 U&& &&arg 형으로 변할 것이고 이는 U&& arg 형입니다. (잘 모르시겠다면 std::move 글 참조)

 

자 이제 다 고쳐진걸까요? 아니요 하나가 더 남았습니다.

std::move 때도 말씀 드렸지만, rvalue가 함수내에 들어가 인자로 사용되게 되면 그 rvalue는 lvalue로 변한다고 적어놨습니다.

그럼 bb를 만들땐 rvalue로 넘겼지만, 생성자로 만들땐 lvalue로 사용되는 겁니다. 

만약 B가 rvalue를 가지고 생성을 하고있었다면 절때 들어가지 않겠죠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;
 
class A {
public:
    A(int& _a) { }
};
class B {
public:
B(const int& b) { }
    B(int&& _b) { }    // 이런, Factory 템플릿은 이 생성자에 절때 들어오지 않아요!
};
 
template <typename T, typename U>
T* Factory(U&& arg) {
    return new T(arg);
}
int main() {
    int i = 1;
    A* aa = Factory<A>(i);
    B* bb = Factory<B>(1);
}
cs

 

아마 bb가 생성될 땐, 자동으로 만들어낸 기본 생성자를 호출하여 생성할 것입니다.

저희가 원한건 이게 아닌 rvalue 생성자를 원했는데요.

이때 사용되는데 std::forward 함수입니다. 이 함수는 

 

 

이렇게 선언되어 있습니다.

즉, lvalue가 넘어왔을땐 static_cast<Ty& &&> 로 캐스팅돼 static_cast<Ty&> 형으로 넘어가게 되고

rvalue가 넘어왓을땐 static_cast<Ty&& &&> 로 캐스팅돼 static_cast<Ty&&> 형으로 넘어가게 됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
 
template <typename T, typename U>
T* Factory(U&& arg) {
    return new T(std::forward<U>(arg));        // 이제 제대로 rvalue 생성자로 넘어갈 수 있어요!
    //return new T(static_cast<U&&>(arg)) 와 같습니다.
}
int main() {
    int i = 1;
    A* aa = Factory<A>(i);
    B* bb = Factory<B>(1);
}
cs

 

return new T(static_cast<U&&>(arg)) 를 사용하셔도 되지만, 좀 더 직관적으로 보이기 위해

return new T(std::forward<U>(arg)) 같은 std::forward를 사용하시는게 좋습니다.

 

 

하아 공부하면서 작성하니 이해가 쏙쏙 오네요.

다음은 람다식에 대해 작성하겠습니다.

 

인용 :http://msdn.microsoft.com/en-us/library/dd293668.aspx

 

'C++ > Modern' 카테고리의 다른 글

C++11) decltype (형식 지정자)  (1) 2019.02.03
C++11) 람다식 (Lambda Expression) 과 std::function  (4) 2019.02.02
c++11) std::move (move semantics)  (2) 2019.02.01
C++11) auto 키워드  (0) 2019.01.30
C++11) 범위 기반 for문 (loop)  (4) 2019.01.27