그 정확한 std::move(x)
더 구체적으로 -를 rvalue에 바로 캐스팅이다 가 xValue A와 반대로, prvalue . 그리고 캐스트라는 이름의 move
사람들이 때때로 사람들을 혼란스럽게 한다는 것도 사실입니다 . 그러나이 네이밍의 의도는 혼동하기위한 것이 아니라 코드를 더 읽기 쉽게 만드는 것입니다.
2002 년의 원래 이동 제안서로move
거슬러 올라간 역사 . 이 백서는 먼저 rvalue 참조를 소개 한 다음보다 효율적인 작성 방법을 보여줍니다 .std::swap
template <class T>
void
swap(T& a, T& b)
{
T tmp(static_cast<T&&>(a));
a = static_cast<T&&>(b);
b = static_cast<T&&>(tmp);
}
역사상이 시점에서 " &&
"가 의미 할 수있는 유일한 것은 논리적이고 이었다는 것을 기억해야합니다 . rvalue 참조 나 lvalue를 rvalue로 캐스트하는 것과 같은 의미는 누구도 알지 못했습니다 (사본처럼 복사 static_cast<T>(t)
하지 않음). 따라서이 코드를 읽는 독자는 자연스럽게 다음과 같이 생각합니다.
나는 어떻게 swap
작동 해야하는지 (일시적으로 복사 한 다음 값을 교환) 알고 있지만 그 추악한 캐스트의 목적은 무엇입니까?!
또한 swap
실제로 모든 종류의 순열 수정 알고리즘에 대한 스탠드 인입니다. 이 토론은 훨씬 더 큽니다 swap
.
그런 다음 제안은 구문 설탕 을 도입하여 구문을static_cast<T&&>
더 읽기 쉬운 것으로 바꾸고 정확한 내용이 아니라 그 이유 를 전달 합니다 .
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
즉 ,에 move
대한 구문 설탕 static_cast<T&&>
이며 이제 코드는 왜 이러한 캐스트가 있는지에 대한 암시를 제공합니다.
역사의 맥락에서,이 시점에서 rvalues와 이동 의미론 사이의 친밀한 연관성을 실제로 이해하는 사람은 거의 없다는 것을 이해해야합니다.
rvalue 인수가 주어지면 이동 시맨틱이 자동으로 작동합니다. rvalue에서 리소스를 이동하는 것은 프로그램의 나머지 부분에서 확인할 수 없기 때문에 이는 완벽하게 안전합니다 ( 다른 사람은 차이를 감지하기 위해 rvalue에 대한 참조를 가지고 있지 않습니다 ).
당시에 swap
다음과 같이 제시된 경우 :
template <class T>
void
swap(T& a, T& b)
{
T tmp(cast_to_rvalue(a));
a = cast_to_rvalue(b);
b = cast_to_rvalue(tmp);
}
그러면 사람들은 그것을보고 말했을 것입니다.
하지만 왜 rvalue로 캐스팅합니까?
요점 :
을 사용하여 move
아무도 묻지 않았습니다.
근데 왜 움직여?
몇 년이 지남에 따라 제안이 구체화되면서 lvalue와 rvalue의 개념 은 오늘날 우리가 보유 하고있는 가치 범주 로 구체화되었습니다 .
( 어둠없이 도둑 맞은 이미지 )
그리고 오늘날 우리 가 왜 하고 있는지 대신 swap
정확히 무엇 을 말하고 싶다면 다음과 같이 보일 것입니다.
template <class T>
void
swap(T& a, T& b)
{
T tmp(set_value_category_to_xvalue(a));
a = set_value_category_to_xvalue(b);
b = set_value_category_to_xvalue(tmp);
}
그리고 모든 사람들이 스스로 질문해야 할 것은 위의 코드가 다음보다 읽기 쉬운 지 여부입니다.
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
또는 원본 :
template <class T>
void
swap(T& a, T& b)
{
T tmp(static_cast<T&&>(a));
a = static_cast<T&&>(b);
b = static_cast<T&&>(tmp);
}
여하튼, 여행가 C ++ 프로그래머는의 의지에서 move
캐스트보다 더 많은 일이 일어나지 않는다는 것을 알아야합니다 . 그리고 초보자 C ++ 프로그래머 는 적어도을 사용 move
하여 의도가 정확히 어떻게 이해 되는지 이해하지 못하더라도 rhs에서 복사 하는 것이 아니라 rhs에서 이동 하려는 의도를 알 수 있습니다 .
또한 프로그래머가 다른 이름으로이 기능을 원하는 경우이 기능에 std::move
대한 독점이 없으며 구현시 이식 할 수없는 언어 마법이 없습니다. 예를 들어 코딩 set_value_category_to_xvalue
하고 싶을 경우 대신 사용하는 것이 간단합니다.
template <class T>
inline
constexpr
typename std::remove_reference<T>::type&&
set_value_category_to_xvalue(T&& t) noexcept
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
C ++ 14에서는 훨씬 간결합니다.
template <class T>
inline
constexpr
auto&&
set_value_category_to_xvalue(T&& t) noexcept
{
return static_cast<std::remove_reference_t<T>&&>(t);
}
따라서 너무 기울어지면 static_cast<T&&>
가장 잘 생각하지만 꾸미십시오. 아마도 새로운 모범 사례를 개발하게 될 것입니다 (C ++은 끊임없이 발전하고 있습니다).
move
생성 된 객체 코드와 관련하여 무엇을 하는가?
이것을 고려하십시오 test
:
void
test(int& i, int& j)
{
i = j;
}
로 컴파일 clang++ -std=c++14 test.cpp -O3 -S
하면 다음 과 같은 객체 코드가 생성됩니다.
__Z4testRiS_: ## @_Z4testRiS_
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
movl (%rsi), %eax
movl %eax, (%rdi)
popq %rbp
retq
.cfi_endproc
이제 테스트가 다음으로 변경되면
void
test(int& i, int& j)
{
i = std::move(j);
}
객체 코드 에는 전혀 변화 가 없습니다 . 이 결과를 다음과 같이 일반화 할 수 있습니다. 사소하게 움직일 수있는 물체 std::move
에는 영향을 미치지 않습니다.
이제이 예제를 보자.
struct X
{
X& operator=(const X&);
};
void
test(X& i, X& j)
{
i = j;
}
이것은 다음을 생성합니다.
__Z4testR1XS0_: ## @_Z4testR1XS0_
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
popq %rbp
jmp __ZN1XaSERKS_ ## TAILCALL
.cfi_endproc
당신이 그것을 __ZN1XaSERKS_
통해 실행 c++filt
하면 : X::operator=(X const&)
. 놀랍지 않습니다. 이제 테스트가 다음으로 변경되면
void
test(X& i, X& j)
{
i = std::move(j);
}
그런 다음 생성 된 오브젝트 코드 에는 여전히 변경 사항이 없습니다 . rvalue로 std::move
캐스트 한 것 외에는 수행하지 않았 j
으며, rvalue X
는의 복사 할당 연산자에 바인딩됩니다 X
.
이제 이동 할당 연산자를 X
다음에 추가하십시오 .
struct X
{
X& operator=(const X&);
X& operator=(X&&);
};
이제 객체 코드 가 변경됩니다.
__Z4testR1XS0_: ## @_Z4testR1XS0_
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
popq %rbp
jmp __ZN1XaSEOS_ ## TAILCALL
.cfi_endproc
를 __ZN1XaSEOS_
통해 실행 c++filt
하면 X::operator=(X&&)
이 (가) 대신 호출됩니다 X::operator=(X const&)
.
그리고 그게 전부입니다 std::move
! 런타임에 완전히 사라집니다. 오버로드의 영향은 컴파일 타임에 과부하가 호출 되는 것을 변경할 수 있습니다.
std::move
실제로 움직입니다.