이번 포스트는 c++11 에 생긴 std::move std::forward 두 함수의 차이랑 사용 용도를 포스트 하겠습니다.
이 두 함수를 알려면 먼저 lvalue와 rvalue에 대한 이해를 이해 해야하는데 여기서는 간단하게 설명을 하고 나중에 따로 포스트 하겠습니다.
lvalue: 이름이 있는 메모리 주소를 가질 수 있는 표현식입니다 대입문의 왼쪽에 올 수 있으며 여러 번 참조될 수 있습니다.
// 예시
int x;
std::string s;
rvalue: 임시적인 값 또는 이름이 없는 표현식입니다 대입문의 오른쪽에만 올 수 있으며 보통 한 번 사용되고 사라집니다.
10;
std::string("hello");
x + y;
간단하게 요약을 하자면 변수 선언 할때
(lvalue) int x = (rvalue) 10;
왼쪽이 lvalue 오른쪽이 rvalue이다.
std::move부터 설명 하자면 인자로 전달 하는 객체는 무조건 rvalue 참조 타입으로 캐스팅 합니다.(xvalue)
조금 이해를 위해서 풀어서 설명 하자면 객체를 복사 하지 않고 데이터를 넘깁니다.
내부 코드롤 보게 된다면
template<typename T>
typename std::remove_reference<T>::type&& move(T&& arg) noexcept {
return static_cast<typename std::remove_reference<T>::type&&>(arg);
}
으로 구현된걸 볼수있다. 내부 적으로 static_cast를 사용해서 인자를 rvalue참조로 변환후 리턴 해준다.
보통적으로 멤버 변수들을 이동 시켜야할때 사용한다.
std::string a = "testdata";
std::string b = "";
std::string c = "";
b = std::move(a);
c = std::move(b);
std::cout << "a =" << a << std::endl;
std::cout << "b =" << b << std::endl;
std::cout << "c =" << c << std::endl;
결과를 보게 된다면 b로 가져가고 c로 가져가서 c에 데이터가 있는 상태 이다.
어떤 함수에 보내서 더이상 데이터를 안쓰거나 하게 될경우 많이 사용하고있다.
std::forward는 인자의 값의 종류를 보존하면서 다른 함수로 전달 할때 사용합니다.
사용하는 이유는
템블릿 함수에서 인자를 받을때 T&&를 많이 사용하는데
이 T&&는 전달되는 인자가 lvalue면 T 는 lvalue 참조 타입으로 추론되고 ravlue면 T는 비참조 타입으로 추론됩니다.
들어오는 타입에 따라서 변경이 되는것이 많습니다.
함수 템플릿 내에서는 T&&로 선언도니 매개변수 자체는 변수명이 있습니다. 그러면 lvalue 타입이라는걸 알수있다.
결국엔 특정 조건에서만 바꿔서 전달 하고 아닌경우에는 그냥 그대로 리턴 하는것이다.
조금 복잡하기에 잘 갈무리된 모두의 코드 링크 걸어두고 코드를 가지고왔다.
#include <iostream>
#include <memory>
#include <utility>
struct A {
A(int&& n) { std::cout << "rvalue overload, n=" << n << "\n"; }
A(int& n) { std::cout << "lvalue overload, n=" << n << "\n"; }
};
class B {
public:
template <class T1, class T2, class T3>
B(T1&& t1, T2&& t2, T3&& t3)
: a1_{std::forward<T1>(t1)},
a2_{std::forward<T2>(t2)},
a3_{std::forward<T3>(t3)} {}
private:
A a1_, a2_, a3_;
};
template <class T, class U>
std::unique_ptr<T> make_unique1(U&& u) {
return std::unique_ptr<T>(new T(std::forward<U>(u)));
}
template <class T, class... U>
std::unique_ptr<T> make_unique2(U&&... u) {
return std::unique_ptr<T>(new T(std::forward<U>(u)...));
}
int main() {
auto p1 = make_unique1<A>(2); // rvalue
int i = 1;
auto p2 = make_unique1<A>(i); // lvalue
std::cout << "B\n";
auto t = make_unique2<B>(2, i, 3);
}
결과를 전체적으로 비교 하자면
std::move: 무조건 rvalue로 만듦
std::forward: 원래 lvalue였으면 lvalue로, rvalue였으면 rvalue로 전달
한다고 이해 하고 있으면 될꺼같다.