std :: move ()는 어떻게 값을 RValues로 전송합니까?


104

의 논리를 완전히 이해하지 못하고 std::move()있습니다.

처음에는 검색했지만 std::move()구조가 어떻게 작동하는지가 아니라 사용 방법에 대한 문서 만있는 것 같습니다 .

내 말은, 템플릿 멤버 함수가 무엇인지 알고 있지만 std::move()VS2010에서 정의를 살펴보면 여전히 혼란 스럽습니다.

std :: move ()의 정의는 다음과 같습니다.

template<class _Ty> inline
typename tr1::_Remove_reference<_Ty>::_Type&&
    move(_Ty&& _Arg)
    {   // forward _Arg as movable
        return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
    }

저에게 가장 이상한 것은 (_Ty && _Arg) 매개 변수입니다. 왜냐하면 아래와 같이 함수를 호출하면

// main()
Object obj1;
Object obj2 = std::move(obj1);

기본적으로 다음과 같습니다.

// std::move()
_Ty&& _Arg = Obj1;

그러나 이미 알고 있듯이 LValue를 RValue 참조에 직접 연결할 수는 없기 때문에 이와 같아야한다고 생각합니다.

_Ty&& _Arg = (Object&&)obj1;

그러나 std :: move ()가 모든 값에 대해 작동해야하기 때문에 이것은 어리석은 일입니다.

그래서 이것이 어떻게 작동하는지 완전히 이해하고 있다고 생각합니다.이 구조체도 살펴 봐야합니다.

template<class _Ty>
struct _Remove_reference
{   // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&>
{   // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&&>
{   // remove rvalue reference
    typedef _Ty _Type;
};

불행히도 여전히 혼란스럽고 이해하지 못합니다.

이것이 C ++에 대한 기본적인 구문 기술이 부족하기 때문이라는 것을 알고 있습니다. 이러한 작업이 어떻게 철저하게 작동하는지, 인터넷에서 얻을 수있는 모든 문서를 환영하는 것 이상으로 알고 싶습니다. (당신이 이것을 설명 할 수 있다면 그것도 굉장 할 것입니다).


31
@NicolBolas 반대로 질문 자체는 OP가 그 진술에서 자신을 과소 판매하고 있음을 보여줍니다. 질문을 던질 수 있도록하는 것은 기본 구문 기술에 대한 OP의 이해 입니다.
Kyle Strand

어쨌든 나는 동의하지 않습니다. 빨리 배우거나 이미 컴퓨터 과학이나 프로그래밍에 대해 전체적으로, 심지어 C ++에서도 잘 이해할 수 있지만 여전히 구문에 어려움을 겪고 있습니다. 메커니즘, 알고리즘 등을 배우는 데 시간을 보내는 것이 좋지만 필요에 따라 구문을 다듬기 위해 참조에 의존하는 것이 기술적으로 명료하지만 복사 / 이동 / 전달과 같은 것을 얕게 이해하는 것보다 낫습니다. move가 무엇을하는지 알기 전에 "물건을 이동하고 싶을 때 std :: move를 언제 어떻게 사용하는지"를 어떻게 알 수 있습니까?
John P

move구현 방식보다는 작동 방식 을 이해하기 위해 여기에 온 것 같습니다 . 이 설명이 정말 유용하다고 생각합니다 : pagefault.blog/2018/03/01/… .
Anton Daneyko

답변:


171

이동 기능부터 시작합니다 (조금 정리했습니다).

template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

더 쉬운 부분부터 시작하겠습니다. 즉, 함수가 rvalue로 호출 될 때입니다.

Object a = std::move(Object());
// Object() is temporary, which is prvalue

우리 move는 다음과 같이 템플릿이 인스턴스화됩니다 :

// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
  return static_cast<remove_reference<Object>::type&&>(arg);
}

이후 remove_reference변환 T&T또는 T&&T, 그리고 Object참조 아니며, 우리 최종 함수이다 :

Object&& move(Object&& arg)
{
  return static_cast<Object&&>(arg);
}

이제 궁금 할 것입니다. 캐스트가 필요한가요? 대답은 : 그렇습니다. 그 이유는 간단합니다. 명명 된 rvalue 참조 lvalue 처리되며 lvalue에서 rvalue 참조로의 암시 적 변환은 표준에 의해 금지됩니다.


movelvalue로 호출하면 다음과 같은 일이 발생합니다 .

Object a; // a is lvalue
Object b = std::move(a);

및 해당 move인스턴스화 :

// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
  return static_cast<remove_reference<Object&>::type&&>(arg);
}

다시 remove_reference변환 Object&하려면 Object우리가 얻을 :

Object&& move(Object& && arg)
{
  return static_cast<Object&&>(arg);
}

이제 까다로운 부분에 도달합니다. Object& &&even은 무엇 을 의미하며 어떻게 lvalue에 바인딩 할 수 있습니까?

완벽한 포워딩을 허용하기 위해 C ++ 11 표준은 다음과 같은 참조 축소에 대한 특수 규칙을 제공합니다.

Object &  &  = Object &
Object &  && = Object &
Object && &  = Object &
Object && && = Object &&

보시 다시피이 규칙에서 Object& &&실제로는 Object&lvalue 바인딩을 허용하는 일반 lvalue 참조 인을 의미 합니다.

따라서 최종 기능은 다음과 같습니다.

Object&& move(Object& arg)
{
  return static_cast<Object&&>(arg);
}

이는 rvalue를 사용한 이전 인스턴스화와 다르지 않습니다. 둘 다 인수를 rvalue 참조로 캐스팅 한 다음 반환합니다. 차이점은 첫 번째 인스턴스화는 rvalue로만 사용할 수있는 반면 두 번째 인스턴스는 lvalue로 작동한다는 것입니다.


remove_reference더 필요한 이유를 설명하기 위해이 기능을 사용해 보겠습니다.

template <typename T>
T&& wanna_be_move(T&& arg)
{
  return static_cast<T&&>(arg);
}

lvalue로 인스턴스화하십시오.

// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
  return static_cast<Object& &&>(arg);
}

위에서 언급 한 참조 축소 규칙을 적용하면 사용할 수없는 함수를 얻을 수 있습니다 move(간단히 말하면 lvalue로 호출하면 lvalue가 반환 됨). 이 기능은 식별 기능입니다.

Object& wanna_be_move(Object& arg)
{
  return static_cast<Object&>(arg);
}

3
좋은 대답입니다. 내가 좌변 것이 평가하는 좋은 아이디어라고 이해하고 있지만 T로서 Object&, 나는이 정말 이루어집니다 몰랐다. 나는 이것이 래퍼 참조를 도입하는 이유라고 생각했기 때문에이 경우 T에도 평가할 것으로 예상했을 것입니다. Objectstd::ref
Christian Rau

2
차이있다 template <typename T> void f(T arg)및 (위키 백과 문서에 무엇이다) template <typename T> void f(T& arg). 첫 번째는 값으로 확인되고 (참조를 전달하려면으로 래핑해야 함 std::ref) 두 번째는 항상 참조로 확인됩니다. 슬프게도 템플릿 인수 추론에 대한 규칙은 다소 복잡하기 때문에 왜 T&&리소 블 하는지 정확한 추론을 제공 할 수 없습니다 Object& &&(그러나 실제로 발생합니다).
Vitus

1
그러나이 직접적인 접근 방식이 효과가없는 이유가 있습니까? template <typename T> T&& also_wanna_be_move(T& arg) { return static_cast<T&&>(arg); }
greggo

1
그래서 remove_reference가 필요한 이유를 설명하지만 함수가 T && 대신 T &&를 가져와야하는 이유를 알 수 없습니다. 이 설명을 올바르게 이해하면 T && 또는 T & 중 어느 쪽이든 remove_reference가 T로 캐스팅 한 다음 &&를 다시 추가 할 것이기 때문에 T &&를 얻든 T &를 얻든 상관 없습니다. 그렇다면 T && 대신 T & (의미 적으로 수락하는 것)를 수락하고 발신자가 T &를 전달할 수 있도록 완벽한 전달에 의존한다고 말하지 않겠습니까?
mgiuca 2014 년

3
@mgiuca : std::movelvalue를 rvalue 로만 캐스팅하려면 예, T&괜찮습니다. 이 트릭은 대부분 유연성을 위해 수행 std::move됩니다. 모든 항목 (rvalue 포함)을 호출 하고 rvalue를 다시 가져올 수 있습니다.
Vitus

4

_Ty는 템플릿 매개 변수이며이 상황에서는

Object obj1;
Object obj2 = std::move(obj1);

_Ty는 "Object &"유형입니다.

이것이 _Remove_reference가 필요한 이유입니다.

더 같을 것입니다

typedef Object& ObjectRef;
Object obj1;
ObjectRef&& obj1_ref = obj1;
Object&& obj2 = (Object&&)obj1_ref;

참조를 제거하지 않았다면 우리가하는 것과 같을 것입니다.

Object&& obj2 = (ObjectRef&&)obj1_ref;

그러나 ObjectRef &&는 Object &로 축소되어 obj2에 바인딩 할 수 없습니다.

이 방법을 줄이는 이유는 완벽한 전달을 지원하기 위해서입니다. 이 문서를 참조하십시오 .


이것이 왜 필요한지에 대한 모든 것을 설명하지는 않습니다 _Remove_reference_. 예를 들어 Object&typedef가 있고 이에 대한 참조를 사용하면 여전히 Object&. &&와 함께 작동하지 않는 이유는 무엇입니까? 가 이다 것과 대답은, 그것은 완벽하게 전달과 관련이있다.
Nicol Bolas 2011 년

2
진실. 그리고 그 대답은 매우 흥미 롭습니다. A & &&는 A &로 축소되므로 (ObjectRef &&) obj1_ref를 사용하려고하면이 경우 대신 Object &를 얻게됩니다.
Vaughn Cato 2011 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.