Eficiencia de C ++ 11 push_back () con std :: move versus emplace_back () para objetos ya construidos

86

En C ++ 11 emplace_back()generalmente se prefiere (en términos de eficiencia) push_back()porque permite la construcción en el lugar, pero ¿sigue siendo así cuando se usa push_back(std::move())con un objeto ya construido?

Por ejemplo, ¿ emplace_back()todavía se prefiere en casos como el siguiente?

std::string mystring("hello world");
std::vector<std::string> myvector;

myvector.emplace_back(mystring);
myvector.push_back(std::move(mystring));
// (of course assuming we don't care about using the value of mystring after)

Además, ¿hay algún beneficio en el ejemplo anterior de hacer en su lugar:

myvector.emplace_back(std::move(mystring));

¿O la mudanza aquí es completamente redundante o no tiene ningún efecto?

Alboroto
fuente
myvector.emplace_back(mystring);copia y no se mueve. Los otros dos se mueven y deberían ser equivalentes.
TC
Consulte también esta pregunta y los resultados: Encuesta solicitada para VC ++ con respecto a la inserción y el
emplazamiento

Respuestas:

116

Veamos qué hacen las diferentes llamadas que proporcionó:

  1. emplace_back(mystring): Esta es una construcción in situ del nuevo elemento con cualquier argumento que haya proporcionado. Dado que proporcionó un lvalue, esa construcción en el lugar de hecho es una construcción de copia, es decir, esto es lo mismo que llamarpush_back(mystring)

  2. push_back(std::move(mystring)): Esto llama a la inserción de movimiento, que en el caso de std :: string es una construcción de movimiento en el lugar.

  3. emplace_back(std::move(mystring)): Esta es nuevamente una construcción en el lugar con los argumentos que proporcionó. Dado que ese argumento es un valor r, llama al constructor de movimiento de std::string, es decir, es una construcción de movimiento en el lugar como en 2.

En otras palabras, si se llama con un argumento de tipo T, ya sea rvalue o lvalue, emplace_backy push_backson equivalentes.

Sin embargo, para cualquier otro argumento, emplace_backgana la carrera, por ejemplo con a char const*en a vector<string>:

  1. emplace_back("foo")requiere string::string(char const*)construcción in situ.

  2. push_back("foo")primero tiene que llamar string::string(char const*)a la conversión implícita necesaria para que coincida con la firma de la función, y luego una inserción de movimiento como en el caso 2. anterior. Por lo tanto es equivalente apush_back(string("foo"))

Arne Mertz
fuente
1
El constructor Move es generalmente más eficiente que los constructores de copia, por lo tanto, usar rvalue (caso 2, 3) es más eficiente que usar un lvalue (caso 1) a pesar de la misma semántica.
Ryan Li
¿qué pasa con tal situación? void foo (cadena && s) {vector.emplace (s); // 1 vector.emplace (std :: move (s)); // 2}
VALOD9
@VALOD esos son los números 1 y 3 de mi lista nuevamente. spuede definirse como una referencia rvalue que se une solo a rvalues, pero dentro de foo, shay un lvalue.
Arne Mertz
1

Emplace_back obtiene una lista de referencias de rvalue e intenta construir un elemento contenedor directamente en su lugar. Puede llamar a emplace_back con todos los tipos que admiten los constructores de elementos de contenedor. Cuando se llama a emplace_back para parámetros que no son referencias de rvalue, "recurre" a las referencias normales y al menos se llama al constructor de copia cuando el parámetro y los elementos del contenedor son del mismo tipo. En su caso, 'myvector.emplace_back (mystring)' debería hacer una copia de la cadena porque el compilador no pudo saber que el parámetro myvector es movible. Así que inserte std :: move lo que le da el beneficio deseado. El push_back debería funcionar tan bien como emplace_back para elementos ya construidos.

Tunichtgut
fuente
2
Esa es una ... forma interesante de describir reenvío / referencias universales.
TC