rvalue reference, perfect forwarding, forward

하루공부·2024년 1월 24일

C++

목록 보기
24/25
post-thumbnail

C++ 아이콘 제작자: Darius Dan - Flaticon


rvalue reference

  • C++에서는 rvalue를 레퍼런스로 받을 수 있게 되었다. (임시객체를 받을 수 있다)
  • && 처럼 &를 2번 사용하여 이를 나타낼 수 있다.
  • rvalue 레퍼런스이니 lvalue 레퍼런스를 받지 못한다

  • 왜 생겼고? 왜 사용하나?
    • 함수에 객체를 전달해 함수 내부에서 새로운 객체에 복사를 하고 그 새로운 객체를 리턴하는 함수
      ㄴ임시객체로 하여금 여러번 복사가 일어나는 경우가 많다.
      ex) 어떤 같은 타입의 객체끼리 대입, 그리고 함수 호출 등에서 너무 빈번하게 복사가 일어남
      ==> 매번 복사를 하는 오버헤드를 처리하고 싶어
      ( 만약 큰 자원을 사용한다면 레퍼런스로 주소만을 전달해 간단하고 적은 오버헤드로 처리)

    임시객체는 해당 범위를 벗어나면 사라진다.
    ==> 근데 사라지는 것을 사용하고 싶어 ( 레퍼런스를 전달하니 객체를 계속 사용가능 )

==> 그래서 임시객체에 대해서 복사 생성자가 아닌 따로 특별하게 처리하는 이동 생성자를 만듦

임시객체 리턴을 복사해서 새로운 객체 만드는게 아니라
임시객체의 데이터들의 주소들을 넘긴다.


이동 생성자 & 이동 대입 연산자

  • 위의 내용을 해결하기 위해 rvalue를 받아 복사가 아닌 데이터를 그대로 이동시킨다.

    간단하게 기존의 데이터를 새로운 객체 내용을 얕은 복사를 수행한다
    (동적할당한 변수는 그냥 그대로 가리키고 일반 변수는 그냥 복사)
    그럼 같은 데이터를 2개의 객체에서 가리키니 기존 객체는 nullptr를 가리켜 임시객체가 소멸되도 메모리 해제를 못하게 막는다. (이렇게 임시객체 내용을 사라지지 않고 계속 유지 가능)


  • 이동 대입 연산자도 이동 생성자와 거의 같다.

    대입 할 때 자기 자신을 자기에 대입할 때는 그냥 *this를 리턴
    아니라면 위 처럼 내용을 이동시킨다. (기본적인 대입 연산자와 같다)
    중요한건 새로운 데이터를 가리키는 것이니 (기존 객체의 입장에선) 기존 데이터 메모리를 해제해줘야 한다.


  • 요지는 그저 단순히 주소만 변경시키는 것이다.

  • 하지만 rvalue 레퍼런스를 이용해 임시객체를 계속 유지하여 사용도 하고 오버헤드도 줄이지만 문제가 있다.

    rvalue 레퍼런스를 전달 받아 함수 내에서 사용을 하는데 함수 내에서는 해당 변수 표현식이 lvalue가 된다.
    함수 내에서는 이름을 가지기 때문이다, 실제로 lvalue로써 활용을 한다.

    rvalue레퍼런스를 다른 곳으로 연속으로 전달을 할 때 해당 rvalue의 성질이 계속 있어야한다

==> 완벽한 전달로 해결한다.



perfect forwarding

  • 위에서 rvalue 레퍼런스의 내용을 그대로 이동 시킬 무언가가 필요하다.
    그것을 완벽한 전달이라고 한다.

    위에서 말한 함수 내부에서 lvalue로써 작동하기 생기는 문제니 그것을 rvalue로 만들면 된다.
    ==> std::move()를 사용하여 바꿀 수 있다.


  • 그런데 이 move에도 문제가 있다.
    ㄴ 바로 rvalue와 lvalue를 구별을 못하는 상황이 있다.

    일반적으로 T&& 에는 rvalue만 들어가고, T&에는 lvalue만 들어간다.
    하지만 템플릿에서는 상황이 달라진다
    rvalue 레퍼런스(T&&)을 받는 템플릿 함수가 있다면 ravlue는 당연하고 lvalue까지 받아서 인스턴스화한다. ==> 이것을 universal reference라고 한다. (보편 참조)

    • 여기서 해당 템플릿으로 2개의 값이 다 작동하는데 rvalue의 경우 내부에서 lvalue로 바뀌니
      move를 사용할 것이다.
    • 근데 진짜 lvalue도 들어올 수 있으니 만약 들어온다면 move로 인해 반대로 lvalue가 rvalue로 바뀐다. (ref함수도 마찬가지)

==> forward함수로 해결한다.



forward

  • 이 함수는 우측값 레퍼런스일 때 마치 move를 적용시킨 것 처럼 작동시켜준다.
    ㄴ 실제로 forward 가 어떻게 생겼나면
template <class S>
S&& forward(typename std::remove_reference<S>::type& a) noexcept {
	return static_cast<S&&>(a);
}

s가 A& 라면

A&&& forward(typename std::remove_reference<A&>::type& a) noexcept {
	return static_cast<A&&&>(a);
}

가 되어서 레퍼런스 겹침 규칙에 따라 A& forward(A& a) noexcept { return static_cast<A&>(a);}가 된다.


  • 그래서 사용은 어떻게 하냐??
template<class T>
void Test(T&& t) {
	std::forward<T>(t);
}

사용을 할 때 꼭 <T> 으로 템플릿 파라미터를 명시해줘야 rvalue 받는 코드는 위와 같이 작동한다.


  • 위에서 말한 겹침 규칙은 C++11에 레퍼런스 규칙이 생겼다.

    typedef int& T;
    T& r1; // int& &; r1 은 int&
    T&& r2; // int & &&; r2 는 int&

    typedef int&& U;
    U& r3; // int && &; r3 는 int&
    U&& r4; // int && &&; r4 는 int&& ==> 이 친구만 &&로 표현됨


공부한 내용 복습

개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!

0개의 댓글