Qual é a diferença entre std :: move e std :: forward

160

Eu vi isso aqui: Move Constructor chamando o Move Constructor de classe base

Alguém poderia explicar:

  1. a diferença entre std::movee std::forward, de preferência com alguns exemplos de código?
  2. Como pensar sobre isso facilmente e quando usar quais
aCuria
fonte
1
Veja também estas duas perguntas relacionadas: Devo usar std :: move ou std :: forward em operadores de movimento / operadores de atribuição? e Como o std :: forward funciona? (além da pergunta duplicada).
Xeo 12/03/12
"Como pensar sobre isso facilmente e quando usar qual" Você usa movequando deseja mover um valor e forwardquando deseja usar o encaminhamento perfeito. Isso não é ciência de foguetes aqui;)
Nicol Bolas
move () executa conversão incondicional, enquanto que forward () executa conversão com base no parâmetro que passou.
RaGa__M

Respostas:

159

std::movepega um objeto e permite tratá-lo como um temporário (um rvalue). Embora não seja um requisito semântico, normalmente uma função que aceita uma referência a um rvalue o invalidará. Quando você vê std::move, indica que o valor do objeto não deve ser usado posteriormente, mas você ainda pode atribuir um novo valor e continuar usando-o.

std::forwardpossui um único caso de uso: para converter um parâmetro de função de modelo (dentro da função) na categoria de valor (lvalue ou rvalue) que o chamador usou para transmiti-lo. Isso permite que os argumentos rvalue sejam transmitidos como rvalues ​​e lvalues ​​sejam transmitidos como lvalues, um esquema chamado "encaminhamento perfeito".

Para ilustrar :

void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }

template< typename t >
/* "t &&" with "t" being template param is special, and  adjusts "t" to be
   (for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
    std::cout << "via std::forward: ";
    overloaded( std::forward< t >( arg ) );
    std::cout << "via std::move: ";
    overloaded( std::move( arg ) ); // conceptually this would invalidate arg
    std::cout << "by simple passing: ";
    overloaded( arg );
}

int main() {
    std::cout << "initial caller passes rvalue:\n";
    forwarding( 5 );
    std::cout << "initial caller passes lvalue:\n";
    int x = 5;
    forwarding( x );
}

Como Howard menciona, também há semelhanças, já que essas duas funções simplesmente são convertidas para o tipo de referência. Mas fora desses casos de uso específicos (que cobrem 99,9% da utilidade dos modelos de referência rvalue), você deve usar static_castdiretamente e escrever uma boa explicação sobre o que está fazendo.

Potatoswatter
fonte
Não sei se é preciso que std::forwardo único caso de uso seja o encaminhamento perfeito de argumentos de função. Eu já tive situações em que desejo encaminhar perfeitamente outras coisas, como membros de objetos.
Geoff Romer
@GeoffRomer Membros de um parâmetro? Você tem um exemplo?
Potatoswatter
Eu publiquei um exemplo como uma pergunta separada: stackoverflow.com/questions/20616958/…
Geoff Romer
Hmm, então se eu entendi direito, isso significa que eu posso escrever uma única função forward () onde forward (5) opera em 5 como se eu a passasse por valor onde forward (x) opera em x como se eu a tivesse passado por referência sem ter que escrever explicitamente uma sobrecarga.
Iheanyi 17/07/2014
@iheanyi Sim, essa é a ideia! Mas tem que ser um modelo, não apenas uma função comum.
Potatoswatter
63

Ambos std::forwarde std::movenada mais são do que elencos.

X x;
std::move(x);

O exemplo acima converte a expressão lvalue xdo tipo X para uma expressão rvalue do tipo X (um valor x para ser exato). movetambém pode aceitar um rvalue:

std::move(make_X());

e, nesse caso, é uma função de identidade: pega um rvalor do tipo X e retorna um rvalor do tipo X.

Com std::forwardvocê pode selecionar o destino até certo ponto:

X x;
std::forward<Y>(x);

Converte a expressão lvalue xdo tipo X para uma expressão do tipo Y. Há restrições sobre o que Y pode ser.

Y pode ser uma base acessível de X, ou uma referência a uma base de X. Y pode ser X ou uma referência a X. Não é possível descartar qualificadores cv com forward, mas é possível adicionar qualificadores cv. Y não pode ser um tipo que é meramente conversível em X, exceto por meio de uma conversão Base acessível.

Se Y for uma referência lvalue, o resultado será uma expressão lvalue. Se Y não for uma referência de lvalue, o resultado será uma expressão de rvalue (xvalue, para ser preciso).

forwardpode aceitar um argumento rvalue apenas se Y não for uma referência lvalue. Ou seja, você não pode converter um rvalue em lvalue. Por razões de segurança, isso geralmente leva a referências pendentes. Mas converter um rvalue para rvalue é aceitável e permitido.

Se você tentar especificar Y para algo que não é permitido, o erro será detectado no tempo de compilação, não no tempo de execução.

Howard Hinnant
fonte
Se eu encaminhar perfeitamente um objeto para uma função usando std::forward, depois que a função for executada, posso usar esse objeto? Estou ciente de que, no caso de std::move, é um comportamento indefinido.
Izmilind
1
Em relação a move: stackoverflow.com/a/7028318/576911 Pois forward, se você passar um lvalue, sua API deve reagir como se recebesse um lvalue. Normalmente, isso significa que o valor não será modificado. Mas se for um valor não constante, sua API pode ter modificado. Se você passar um rvalue, isso normalmente significa que sua API pode ter sido movida a partir dela e, portanto, seria aplicável o stackoverflow.com/a/7028318/576911 .
Howard Hinnant
21

std::forwardé usado para encaminhar um parâmetro exatamente da maneira como ele foi passado para uma função. Assim como mostrado aqui:

Quando usar std :: forward para encaminhar argumentos?

Usar std::moveoferece um objeto como um rvalue, para possivelmente corresponder a um construtor de movimento ou uma função que aceite rvalues. Isso é feito std::move(x)mesmo que xnão seja um valor próprio.

Bo Persson
fonte