É correto que std::move(x)seja apenas uma conversão para rvalue - mais especificamente para um xvalue , em oposição a um prvalue . E também é verdade que ter um elenco chamado moveàs vezes confunde as pessoas. No entanto, a intenção dessa nomeação não é confundir, mas sim tornar seu código mais legível.
A história moveremonta à proposta original de mudança em 2002 . Este artigo apresenta primeiro a referência rvalue e, em seguida, mostra como escrever uma descrição mais eficiente 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);
}
É preciso lembrar que, neste ponto da história, a única coisa que " &&" poderia significar era lógica e . Ninguém estava familiarizado com as referências rvalue, nem com as implicações de converter um lvalue em um rvalue (sem fazer uma cópia como static_cast<T>(t)faria). Portanto, os leitores desse código pensariam naturalmente:
Eu sei como swapdeve funcionar (copie para temporário e depois troque os valores), mas qual é o propósito desses lançamentos feios ?!
Observe também que swapé realmente apenas um substituto para todos os tipos de algoritmos de modificação de permutação. Essa discussão é muito , muito maior que swap.
Em seguida, a proposta introduz o açúcar de sintaxe, que substitui o static_cast<T&&>por algo mais legível que transmite não o que é preciso , mas o porquê :
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
Ou seja, moveé apenas o açúcar da sintaxe static_cast<T&&>, e agora o código é bastante sugestivo sobre o porquê desses elencos existem: ativar a semântica de movimento!
É preciso entender que, no contexto da história, poucas pessoas realmente entenderam a conexão íntima entre rvalues e semântica de movimento (embora o artigo tente explicar isso também):
A semântica de movimento entrará em jogo automaticamente quando receberem argumentos de rvalue. Isso é perfeitamente seguro porque a movimentação de recursos de um rvalue não pode ser percebida pelo restante do programa ( ninguém mais tem uma referência ao rvalue para detectar uma diferença ).
Se na época swapfosse apresentado assim:
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);
}
Então as pessoas teriam olhado para isso e dito:
Mas por que você está lançando um valor para o valor?
O ponto principal:
Como era move, ninguém nunca perguntou:
Mas por que você está se mudando?
Com o passar dos anos e a proposta foi refinada, as noções de lvalue e rvalue foram refinadas nas categorias de valor que temos hoje:

(imagem descaradamente roubada de dirkgently )
E então hoje, se quiséssemos swapdizer com precisão o que está fazendo, em vez de por que , deveria parecer mais:
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);
}
E a pergunta que todos deveriam se perguntar é se o código acima é mais ou menos legível do que:
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
Ou até o original:
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);
}
De qualquer forma, o programador C ++ deve saber que, sob o capô de move, nada mais está acontecendo do que um elenco. E o programador iniciante em C ++, pelo menos com move, será informado de que a intenção é sair do rhs, em vez de copiar do rhs, mesmo que eles não entendam exatamente como isso é realizado.
Além disso, se um programador desejar essa funcionalidade com outro nome, ele std::movenão possuirá monopólio dessa funcionalidade e não haverá mágica de linguagem não portátil envolvida em sua implementação. Por exemplo, se alguém quiser codificar set_value_category_to_xvaluee usar isso, é trivial fazer isso:
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);
}
No C ++ 14, fica ainda mais conciso:
template <class T>
inline
constexpr
auto&&
set_value_category_to_xvalue(T&& t) noexcept
{
return static_cast<std::remove_reference_t<T>&&>(t);
}
Portanto, se você é tão inclinado, decore o static_cast<T&&>que achar melhor e, talvez, acabe desenvolvendo uma nova melhor prática (o C ++ está em constante evolução).
Então, o que movefaz em termos de código de objeto gerado?
Considere isto test:
void
test(int& i, int& j)
{
i = j;
}
Compilado com clang++ -std=c++14 test.cpp -O3 -S, isso produz este código de objeto:
__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
Agora, se o teste for alterado para:
void
test(int& i, int& j)
{
i = std::move(j);
}
Não há absolutamente nenhuma alteração no código do objeto. Pode-se generalizar esse resultado para: Para objetos trivialmente móveis , std::movenão tem impacto.
Agora vamos ver este exemplo:
struct X
{
X& operator=(const X&);
};
void
test(X& i, X& j)
{
i = j;
}
Isso gera:
__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
Se você executar __ZN1XaSERKS_através c++filtela produz: X::operator=(X const&). Nenhuma surpresa aqui. Agora, se o teste for alterado para:
void
test(X& i, X& j)
{
i = std::move(j);
}
Ainda não há nenhuma alteração no código do objeto gerado. std::movefez nada além de converter jem um rvalue e, em seguida, esse rvalue Xse liga ao operador de atribuição de cópia de X.
Agora vamos adicionar um operador de atribuição de movimento a X:
struct X
{
X& operator=(const X&);
X& operator=(X&&);
};
Agora o código objeto faz a mudança:
__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
Correndo __ZN1XaSEOS_através c++filtrevela que X::operator=(X&&)está sendo chamado em vez de X::operator=(X const&).
E é só isso std::move! Desaparece completamente em tempo de execução. Seu único impacto é no tempo de compilação, onde pode alterar a sobrecarga chamada.
std::moverealmente se move ..