왼쪽 값과 오른쪽 값의 개념
C++에서는 왼쪽 값(lvalue)과 오른쪽 값(rvalue)에 대한 명확한 정의가 없지만, 일반적으로 사용되는 기준은 다음과 같다. 이름이 있는 식은 왼쪽 값이며, 이름이 없는 임시 객체는 오른쪽 값이다. 왼쪽 값은 & 연산자로 주소를 취할 수 있지만, 오른쪽 값은 불가능하다.
예를 들어, 변수 선언이나 함수 호출 결과 등이 왼쪽 값이 되며, 상수 리터럴이나 함수 반환값 같은 임시 값은 오른쪽 값으로 간주된다.
// 왼쪽 값 예시
int value = 10;
int& get_ref() { return value; }
get_ref() = 20; // OK: get_ref()는 왼쪽 값
int* ptr = &get_ref(); // OK: 주소 접근 가능
// 오른쪽 값 예시
int func() { return 42; }
int x = func(); // OK: func()는 오른쪽 값
// int* p = &func(); // 오류: 오른쪽 값에 대해 주소 추출 불가
x = 50; // OK: 50은 오른쪽 값
오른쪽 값 참조와 이동 의미론
C++11부터 도입된 T&&는 오른쪽 값 참조로, 임시 객체의 자원을 효율적으로 이동할 수 있도록 한다. 이는 std::move와 함께 사용되며, 특히 복사 대신 이동 생성자를 호출하기 위한 핵심 메커니즘이다.
예를 들어, 클래스의 이동 생성자는 다음과 같이 선언된다:
class MyClass {
public:
MyClass(MyClass&& other) noexcept {
// other의 데이터를 직접 이동 (복사 방지)
}
};
참조 접합과 완벽 전달의 핵심
std::forward의 작동 원리는 참조 접합(Reference Collapsing) 규칙에 기반한다. 이 규칙은 템플릿 매개변수에서 발생하는 참조 타입이 어떻게 결합되는지를 정의한다.
참조 접합 규칙은 다음과 같다:
T&&→T&(참조의 참조)T&→T&T&&→T&&
이 규칙을 통해, 템플릿 인자에서 전달된 참조의 본질적인 성격(왼쪽 값인지 오른쪽 값인지)을 유지할 수 있다.
완벽 전달 예제 분석
다음 코드는 std::forward가 어떻게 원래 인자의 참조 성격을 보존하는지를 보여준다.
#include <iostream>
#include <memory>
#include <utility>
struct A {
A(int&& n) { std::cout << "오른쪽 값 오버로드, n=" << n << '\n'; }
A(int& n) { std::cout << "왼쪽 값 오버로드, 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); // 오른쪽 값 전달 → 오른쪽 값 오버로드
int i = 1;
auto p2 = make_unique1<A>(i); // 왼쪽 값 전달 → 왼쪽 값 오버로드
std::cout << "B\n";
auto t = make_unique2<B>(2, i, 3);
}
출력 결과:
오른쪽 값 오버로드, n=2
왼쪽 값 오버로드, n=1
B
오른쪽 값 오버로드, n=2
왼쪽 값 오버로드, n=1
오른쪽 값 오버로드, n=3
이 결과에서 알 수 있듯이, B의 생성자 인자로 전달된 각 매개변수는 원래의 참조 성격을 그대로 유지하며, std::forward가 참조 접합을 통해 이를 정확히 전달한다.
결국, std::forward는 템플릿 매개변수의 참조 특성을 유지하고, 해당 특성에 따라 적절한 오버로드를 선택하게 함으로써, 전달 과정에서 왼쪽/오른쪽 값의 본질을 완전히 보존하는 기능을 수행한다. 이것이 바로 '완벽 전달'의 핵심이다.