Effective C++/Effective Modern C++

[Effective Modern C++] 24. 보편 참조와 rValue

김디트 2025. 3. 11. 11:11
728x90

항목 24. 보편 참조와 rValue를 구별하라

 

 

 

T&& 라는 표현
  • 반드시 rValue 참조가 아니다.
void f(Widget&& param); // rValue
Widget&& var1 = Widget(); // rValue

auto&& var2 = var1; // rValue 아님

template<typename T>
void f(std::vector<T>&& param); // rValue

template<typename T>
void f(T&& param); // rValue 아님
  • 두 가지 의미
    • rValue 참조
    • 보편 참조(universal reference)
      • rValue 참조 혹은 lValue 참조 둘 중 하나라는 뜻
      • 때에 따라서 lValue처럼 행동할 때도 있다.
      • 거의 모든 것에 묶을 수 있다.

 

 

 

보편 참조의 두 가지 문맥
  • 함수 템플릿 매개변수
template<typename T>
void f(T&& param); // 보편 참조
  • auto 선언
auto&& var2 = var1; // 보편 참조

 

 

 

 

타입 추론이 일어날 때는 보편 참조이다.
  • 함수 템플릿에서는 param의 타입이 추론된다.
  • var2의 선언에서는 var2의 타입이 추론된다.
  • 타입 추론이 일어나지 않는다면 rValue 참조이다.
void f(Widget&& param); // rValue

Widget&& var1 = Widget(); // rValue
  • 보편 참조는 참조이므로 반드시 초기화해야 한다.
template<typename T>
void f(T&& param); // 보편 참조

Widget w;

f(w); // lValue 전달. param의 타입은 Widget&(즉, lValue)

f(std::move(w)); // rValue 전달. param의 타입은 Widget&&(즉, rValue)

 

 

 

보편 참조이기 위해서는 반드시 T&&의 형태여야 한다.
  • 아래의 경우는 rValue이다.
template<typename T>
void f(std::vector<T>&& param); // 타입 추론이 발생하지만 rValue

std::vector<int> v;
f(v); // 컴파일 에러. lValue를 넘길 수 없다.
  • const 한정사 하나만 붙여도 참조는 보편 참조가 될 수 없다.
template<typename T>
void f(const T&& param); // 보편 참조가 아니라 rValue

 

 

 

템플릿 안에서 T&&를 발견한다고 해도 반드시 보편 참조는 아니다.
  • 반드시 타입 추론이 일어난다는 보장이 없기 때문이다.
template<class T, class Allocator = allocator<T>>
class vector
{
public:
    void push_back(T&& x); // 보편 참조가 아니다.
    ...
};
  • vector 클래스를 사용한 인스턴스가 생성되면 그 시점에 타입이 고정되기 때문이다.
std::vector<Widget> v;
// 위의 경우 void push_back(Widget&& x); 로 함수는 고정되며
// 타입 추론이 발생할 여지가 없다.
  • 반면 emplace_back은 보편 참조를 사용한다. 타입 추론이 발생하기 때문이다.
templace<class T, class Allocator = allocator<T>>
class vector
{
public:
    template<calss.. Args>
    void emplace_back(Args&&... args); // args는 보편 참조
    ...
};

 

 

 

auto&& 타입으로 선언된 변수는 보편 참조이다.
  • 반드시  타입 추론이 발생하며
  • 형태(T&&)가 정확하기 때문이다.
  • C++14 람다 작성 시 많이 발견할 수 있다.
auto timeFucInvocation = [](auto&& func, auto&&... params)
{
    타이머를 시작한다.
    std::forward<decltype(func)>(func)( // 항목 33 참조
        std::forward<decltype(params)>(params)...
    );
    타이머를 정지하고 경과 시간을 기록한다.
};

 

728x90