rvalue는 일반적으로 "임시 값", "이름 없는 값"으로
즉, 한 번 쓰고 버리는 값이라고 생각하면 된다.
그렇기에 lvalue 레퍼런스를 하듯 rvalue 레퍼런스를 사용할 경우 오류가 발생할 수 있다. 따라서 &&라는 기호를 사용하여 rvalue 레퍼런스를 사용할 수 있다.
왜 사용하는 것일까?
불필요한 복사를 피해서 성능(메모리, 속도...)을 크게 향상시키기 위해서이다.
복사를 할 경우 깊은 복사는 비용이 매우 크다. (메모리 할당, 데이터 복제 등)
이동은 그냥 포인터만 옮기면 되기 때문이다. (자원 뺏기)
lvalue를 rvalue로 형 변환 해주는 역할 -> 이동 연산자 (&& a)를 사용하는데 쓰임.
우측값 레퍼런스를 할 때 쓰인다.
#include <utility> // for std::move
#include <iostream>
template<typename T>
class tt
{
int a;
public:
// 기본 생성자
tt() : a(0) {}
// 복사 생성자
tt(const tt& t)
{
a = t.a;
std::cout << "복사 생성자" << std::endl;
}
// 이동 생성자
tt(tt&& t)
{
a = t.a; // int는 move 의미 없지만 일단 값 복사
std::cout << "이동 생성자" << std::endl;
}
};
int main()
{
tt<int> t; // 기본 생성자
tt<int> t2(std::move(t)); // 이동 생성자
}
✅ std::forward란?
"넘겨받은 값을 원래의 속성(lvalue인지 rvalue인지) 그대로 다음 함수에 전달하는 것"
이걸 perfect forwarding (완벽 전달)이라고 한다.
🔍 왜 필요한가?
📦 일반적인 템플릿 함수에서
template <typename T>
void wrapper(T arg) {
callee(arg); // 문제 발생 가능
}
여기서 arg는 항상 좌측값(lvalue)로 취급됨
원래 rvalue였던 값도 lvalue로 바뀜!
즉, 원래 이렇게 부르면:
wrapper(std::string("hi")); // <- 이건 rvalue!
그런데 callee(arg)로 넘기면 arg는 이름 있는 변수 → lvalue
→ 그래서 callee는 복사 생성자를 호출하게 됨 ❌
✅ 해결법: std::forward
template <typename T>
void wrapper(T&& arg) {
callee(std::forward<T>(arg)); // 💥 여기서 원래 성질 유지됨!
}
여기서 T&&는 universal reference (또는 forwarding reference)
std::forward(arg)는
T가 lvalue면 → lvalue로 전달
T가 rvalue면 → rvalue로 전달
즉, 전달받은 값의 원래 성격(lvalue/rvalue)을 그대로 전달하는 게 핵심!