¿Por qué todavía puedo acceder a un objeto que se ha movido?

#include  struct Foo{ int i = 10; }; Foo useless_move(Foo &&f){ return f; } int main() { Foo f = Foo(); fi = 100; Foo f1 = useless_move(std::move(f)); std::cout << fi; std::cout << f1.i; } 

¿Por qué todavía puedo acceder a f, después de mover el valor con Foo f1 = useless_move(std::move(f)); ¿No transferí la propiedad de f a f1?

Escribir std::move no hace mágicamente que ocurra un movimiento; es realmente mal llamado en ese sentido. Todo lo que hace es darle una referencia de valor.

Los movimientos ocurren cuando se pasa una referencia rvalue a un objeto con un constructor de movimiento, que se escribe para intercambiar recursos indirectos, como contenedores y controladores de archivos, en lugar de copiarlos en profundidad (o no se pueden copiar correctamente).

En este caso, solo tienes un int .
No hay constructor de movimientos, nada que mover y ningún movimiento realizado.

E incluso si existiera , el estado de un objeto que se acaba de mover no se especifica, no se garantiza que sea … bueno, sea lo que sea lo que estaba esperando.

Foo::i es literalmente una parte de Foo, y no se puede regalar. Como tu arm Mover recursos de una instancia a otra solo es útil para los punteros a recursos externos que posee el objeto:

 #include  struct Foo{ int* i; //pointer to external resource Foo() :i(new int(10)) {} Foo(const Foo& rhs) :i(new int(*(rhs.i))) {} Foo(Foo&& rhs) :i(rhs.i) noexcept {rhs.i = nullptr;} Foo& operator=(const Foo& rhs) {int* t=new int(*(rhs.i)); delete i; i=t; return *this;} Foo& operator=(Foo&& rhs) noexcept {delete i; i=rhs.i; rhs.i=nullptr; return *this;} ~Foo() {delete i;} }; 

Además, hay un pequeño número de peculiaridades extrañas en los valores, y golpeas a otro:

 Foo useless_move(Foo &&f){ return f; } int main() { Foo f; Foo f1 = useless_move(std::move(f)); } 

Pasaste f a std::move , que devuelve una referencia de valor. Esta es una referencia a f , con la “nota” de la que es seguro moverse. No se ha hecho ningún movimiento. Esta referencia luego se pasa a la función useless_move , que acepta una referencia rvalue. Es decir, acepta una referencia a un Foo , con la nota de que es seguro moverse. A continuación, asigna a esta referencia el nombre f dentro de la función. No se ha hecho ningún movimiento. Aquí está la parte extraña: f no es una referencia de valor. Es una referencia de valor. La && en la statement significa que eso es lo que la función acepta , pero dentro de la función, se trata como un Foo& . Entonces f es una referencia regular dentro de la función. Entonces, return f más un tipo de retorno de Foo le dice a la función que devuelva un Foo construido a partir de la referencia f , de la cual no es seguro moverse. Así que hace una copia. Todavía no se ha hecho ningún movimiento. Finalmente, Foo f1 = ... construye la variable f1 partir del valor de retorno. Este (¡por fin!) Es un movimiento (de la copia, no del original). Excepto que el comstackdor puede detectar que una variable se está construyendo a partir de un temporal, por lo que probablemente elides se mueva, y Foo construye la variable directamente. Tan solo ha ocurrido un movimiento teórico.

std::move() no realiza un movimiento, es literalmente un lanzamiento a una referencia de valor. Hay un solo movimiento que puede ocurrir, y está en la línea Foo f = Foo() asumiendo que las optimizaciones no tienen lugar. Para hacer que suceda un movimiento toma tu argumento por valor:

 Foo useless_move(Foo f); Foo f1 = useless_move(std::move(f)); 

Además, los movimientos para tipos primitivos son una copia, por lo que aún no verás ninguna diferencia.