c++ - Se devuelven los locales automáticamente xvalues
c++11 move-semantics (3)
A pesar de ambos, return std::move(local)
y return local
, trabaje en el sentido de que sí compila, su comportamiento es diferente. Y probablemente solo este último fue pensado.
Si escribe una función que devuelve un std::vector<string>
, tiene que devolver un std::vector<string>
y exactamente eso. std::move(local)
tiene el tipo std::vector<string>&&
que no es un std::vector<string>
por lo que se debe convertir utilizando el constructor move.
La norma dice en 6.6.3.2:
El valor de la expresión se convierte implícitamente al tipo de retorno de la función en la que aparece.
Eso significa que return std::move(local)
es igualvalent a
std::vector<std::string> converted(std::move(local); // move constructor
return converted; // not yet a copy constructor call (which will be elided anyway)
mientras que el return local
solo es return local
return local; // not yet a copy constructor call (which will be elided anyway)
Esto le ahorra una operación.
Para darte un breve ejemplo de lo que eso significa:
struct test {
test() { std::cout << " construct/n"; }
test(const test&) { std::cout << " copy/n"; }
test(test&&) { std::cout << " move/n"; }
};
test f1() { test t; return t; }
test f2() { test t; return std::move(t); }
int main()
{
std::cout << "f1():/n"; test t1 = f1();
std::cout << "f2():/n"; test t2 = f2();
}
Esto dará salida
f1():
construct
f2():
construct
move
A continuación de un comentario que hice sobre esto:
pasar std :: vector al constructor y mover la semántica ¿Es necesario std::move
en el siguiente código para garantizar que el valor devuelto sea un xvalor?
std::vector<string> buildVector()
{
std::vector<string> local;
// .... build a vector
return std::move(local);
}
Tengo entendido que esto es necesario. std::unique_ptr
veces he visto esto usado cuando std::unique_ptr
un std::unique_ptr
desde una función, sin embargo GManNickG hizo el siguiente comentario:
Tengo entendido que en una declaración de retorno, todas las variables locales son automáticamente valores de x (valores que expiran) y se moverán, pero no estoy seguro si eso solo se aplica al objeto devuelto. Así que OP debería seguir adelante y poner eso allí hasta que esté más seguro de que no debería ser así. :)
¿Alguien puede aclarar si el std::move
es necesario?
¿Es el compilador de comportamiento dependiente?
Creo que la respuesta es no. Aunque oficialmente solo es una nota, §5 / 6 resume qué expresiones son / no son valores de x:
Una expresión es un xvalor si es:
- el resultado de llamar a una función, ya sea implícita o explícitamente, cuyo tipo de retorno es una referencia rvalue al tipo de objeto,
- una conversión a una referencia rvalue al tipo de objeto,
- una expresión de acceso de miembro de clase que designa un miembro de datos no estáticos de tipo no de referencia en el que la expresión de objeto es un valor x, o
- Una expresión de. * puntero a miembro en la que el primer operando es un xvalue y el segundo es un puntero al miembro de datos.
En general, el efecto de esta regla es que las referencias de rvalue con nombre se tratan como lvalues y las referencias de rvalue sin nombre a los objetos se tratan como xvalues; Las referencias de rvalue a las funciones se tratan como lvalues ya sea nombrados o no.
El primer punto de bala parece aplicarse aquí. Dado que la función en cuestión devuelve un valor en lugar de una referencia de valor, el resultado no será un valor de x.
Se le garantiza que el valor local
se devolverá como un valor en esta situación. Por lo general, los compiladores realizarían la optimización del valor de retorno, incluso antes de que esto se convierta en un problema, y probablemente no vería ningún movimiento real, ya que el objeto local
se construiría directamente en el sitio de la llamada.
Una nota relevante en 6.6.3 ["La declaración de retorno"] (2):
Una operación de copia o movimiento asociada con una declaración de devolución puede eliminarse o considerarse como un valor de r para la resolución de sobrecarga al seleccionar un constructor (12.8).
Para aclarar, es decir que el objeto devuelto puede ser construido con movimiento desde el objeto local (aunque en la práctica RVO omitirá este paso por completo). La parte normativa de la norma es 12.8 ["Copiar y mover objetos de clase"] (31, 32), en elision de copia y valores (gracias @Mankarse!).
Aquí hay un ejemplo tonto:
#include <utility>
struct Foo
{
Foo() = default;
Foo(Foo const &) = delete;
Foo(Foo &&) = default;
};
Foo f(Foo & x)
{
Foo y;
// return x; // error: use of deleted function ‘Foo::Foo(const Foo&)’
return std::move(x); // OK
return std::move(y); // OK
return y; // OK (!!)
}
Contraste esto con la devolución de una referencia de valor real:
Foo && g()
{
Foo y;
// return y; // error: cannot bind ‘Foo’ lvalue to ‘Foo&&’
return std::move(y); // OK type-wise (but undefined behaviour, thanks @GMNG)
}