왜 std :: move가 이름이`std :: move`입니까?


128

C ++ 11 std::move(x)함수는 실제로 아무것도 움직이지 않습니다. 그것은 단지 r- 값으로의 캐스트입니다. 왜 이렇게 되었습니까? 이것은 오도되지 않습니까?


설상가상으로, 3 개의 인수는 std::move실제로 움직입니다.
Cubbi

그리고 C ++ 98 / 03 / 11 std::char_traits::move:-)를 잊지 마십시오
Howard Hinnant

30
내가 가장 좋아하는 것은 std::remove()요소를 제거하지 않는 것 erase()입니다. 컨테이너에서 실제로 해당 요소를 제거하려면 여전히 호출 해야합니다. 따라서 move움직이지 remove않고 제거하지 않습니다. 에 대한 이름 mark_movable()을 선택했을 것 입니다 move.
Ali

4
@Ali mark_movable()혼란 스러울 것 입니다. 실제로 부작용이없는 곳에서 지속적인 부작용이 있음을 시사합니다.
finnw

답변:


178

그 정확한 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! 런타임에 완전히 사라집니다. 오버로드의 영향은 컴파일 타임에 과부하가 호출 되는 것을 변경할 있습니다.


7
그 그래프에 대한 점이 여기 있습니다 : 나는 digraph D { glvalue -> { lvalue; xvalue } rvalue -> { xvalue; prvalue } expression -> { glvalue; rvalue } }공공의 이익을 위해 그것을 재현했다 :) 여기에 SVG로
sehe

7
이것이 여전히 자전거 타기에 개방되어 있습니까? 나는 제안한다 allow_move;)
dyp

2
@dyp 내가 가장 좋아하는 것은 여전히 movable입니다.
Daniel Frey

6
스콧 마이어스는 이름을 변경 제안 std::movervalue_cast: youtube.com/...
nairware

6
rvalue는 이제 prvalue와 xvalue를 모두 참조하므로 rvalue_cast의미가 모호합니다. 어떤 rvalue가 반환됩니까? xvalue_cast여기서 일관된 이름이 될 것입니다. 불행히도 현재 대부분의 사람들은 자신이하는 일을 이해하지 못합니다. 몇 년 후, 나의 진술은 희망적으로 거짓이 될 것입니다.
Howard Hinnant

20

OP의 질문에 대한 직접적인 답변 인 B. Stroustrup이 작성한 C ++ 11 FAQ 의 인용문을 여기에 남겨 드리겠습니다.

move (x)는 "x를 rvalue로 취급 할 수 있습니다"를 의미합니다. move ()가 rval ()이라고 불리면 더 좋았을 지 모르지만, 이제는 move ()가 몇 년 동안 사용되었습니다.

그건 그렇고, 나는 정말로 FAQ를 즐겼습니다-읽을 가치가 있습니다.


2
다른 답변에서 @HowardHinnant의 의견을 표절하려면 Stroustrup 답변이 정확하지 않습니다. 이러한 rvalue에는 prvalues ​​및 xvalue가 있으며 std :: move는 실제로 xvalue 캐스트입니다.
einpoklum
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.