push_back vs emplace_back

762

Estoy un poco confundido con respecto a la diferencia entre push_backy emplace_back.

void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);

Como hay una push_backsobrecarga tomando una referencia de valor no entiendo en qué se emplace_backconvierte el propósito .

ronag
fuente
11
Algunas buenas lecturas aquí: open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2642.pdf
Johan Kotlinski
16
Tenga en cuenta que (como Thomas dice a continuación), el código en la pregunta proviene de la emulación de MSVS de C ++ 0x, no de lo que realmente es C ++ 0x.
me22
55
Un mejor documento para leer sería: open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf . N2642 está redactado principalmente para el estándar; N2345 es el documento que explica y motiva la idea.
Alan
Tenga en cuenta que incluso en MSVC10 hay una template <class _Valty> void emplace_back(_Valty&& _Val)versión que toma una referencia universal que proporciona un reenvío perfecto a los explicitconstructores de un solo argumento.
joki
Relacionado: ¿Hay algún caso en el que push_backsea ​​preferibleemplace_back ? El único caso en el que puedo pensar es si una clase fuera de alguna manera copiable ( T&operator=(constT&)) pero no construible ( T(constT&)), pero no puedo pensar por qué alguien querría eso.
Ben

Respuestas:

569

Además de lo que dijo el visitante:

La función void emplace_back(Type&& _Val)proporcionada por MSCV10 no es conforme y es redundante, ya que, como ha notado, es estrictamente equivalente apush_back(Type&& _Val) .

Pero la forma real de C ++ 0x emplace_backes realmente útil:void emplace_back(Args&&...) :;

En lugar de tomar un value_type , toma una lista variada de argumentos, lo que significa que ahora puede reenviar perfectamente los argumentos y construir directamente un objeto en un contenedor sin ningún tipo temporal.

Eso es útil porque no importa cuánta inteligencia RVO y movimiento semántico traigan a la mesa, todavía hay casos complicados en los que es probable que un push_back haga copias (o movimientos) innecesarios. Por ejemplo, con la insert()función tradicional de a std::map, debe crear un temporal, que luego se copiará en a std::pair<Key, Value>, que luego se copiará en el mapa:

std::map<int, Complicated> m;
int anInt = 4;
double aDouble = 5.0;
std::string aString = "C++";

// cross your finger so that the optimizer is really good
m.insert(std::make_pair(4, Complicated(anInt, aDouble, aString))); 

// should be easier for the optimizer
m.emplace(4, anInt, aDouble, aString);

Entonces, ¿por qué no implementaron la versión correcta de emplace_back en MSVC? En realidad, también me molestó hace un tiempo, así que hice la misma pregunta en el blog de Visual C ++ . Aquí está la respuesta de Stephan T Lavavej, el responsable oficial de la implementación de la biblioteca estándar de Visual C ++ en Microsoft.

P: ¿Las funciones de emplazamiento beta 2 son solo una especie de marcador de posición en este momento?

R: Como ya sabrá, las plantillas variadas no se implementan en VC10. Los simulamos con maquinaria de preprocesador para cosas como make_shared<T>() tuplas y cosas nuevas <functional>. Esta maquinaria de preprocesador es relativamente difícil de usar y mantener. Además, afecta significativamente la velocidad de compilación, ya que tenemos que incluir repetidamente subencabezados. Debido a una combinación de nuestras limitaciones de tiempo y problemas de velocidad de compilación, no hemos simulado plantillas variadas en nuestras funciones de emplazamiento.

Cuando se implementan plantillas variadas en el compilador, puede esperar que las aprovechemos en las bibliotecas, incluidas nuestras funciones de ubicación. Nos tomamos muy en serio la conformidad, pero desafortunadamente no podemos hacer todo de una vez.

Es una decisión comprensible. Todos los que intentaron emular una plantilla variada con trucos horribles del preprocesador saben lo repugnante que es esto.

Thomas Petit
fuente
101
Esa aclaración de que es un problema de MSVS10, no un problema de C ++ es la parte más importante aquí. Gracias.
me22
11
Creo que su última línea de código C ++ no funcionará. pair<const int,Complicated>no tiene un constructor que tome un int, otro int, un doble y como cuarto parámetro una cadena. Sin embargo, puede construir directamente este objeto par utilizando su constructor por partes. La sintaxis será diferente, por supuesto:m.emplace(std::piecewise,std::forward_as_tuple(4),std::forward_as_tuple(anInt,aDouble,aString));
sellibitze
3
Las plantillas felizmente variadas estarán en VS2013, ahora en vista previa.
Daniel Earwicker
11
¿Debería actualizarse esta respuesta para reflejar los nuevos desarrollos en vs2013?
Becko
66
Si está utilizando Visual Studio 2013 o posterior ahora , debe tener soporte para "real" emplace_backsiempre que se haya implementado en Visual C ++ cuando se agregaron plantillas variadas: msdn.microsoft.com/en-us/library/hh567368. aspx
kayleeFrye_onDeck
200

emplace_backNo debería tomar un argumento de tipo vector::value_type, sino argumentos variados que se envían al constructor del elemento adjunto.

template <class... Args> void emplace_back(Args&&... args); 

Es posible pasar un mensaje value_typeque se enviará al constructor de la copia.

Debido a que reenvía los argumentos, esto significa que si no tiene rvalue, esto significa que el contenedor almacenará una copia "copiada", no una copia movida.

 std::vector<std::string> vec;
 vec.emplace_back(std::string("Hello")); // moves
 std::string s;
 vec.emplace_back(s); //copies

Pero lo anterior debe ser idéntico a lo que push_backhace. Probablemente esté destinado a casos de uso como:

 std::vector<std::pair<std::string, std::string> > vec;
 vec.emplace_back(std::string("Hello"), std::string("world")); 
 // should end up invoking this constructor:
 //template<class U, class V> pair(U&& x, V&& y);
 //without making any copies of the strings
visitante
fuente
2
@David: pero tienes un salcance movido , ¿no es peligroso?
Matthieu M.
2
No es peligroso si ya no planea usar s por su valor. El movimiento no invalida s, el movimiento solo robará la asignación de memoria interna ya realizada en s y la dejará en un estado predeterminado (sin picadura asignada) que cuando se destruya estará bien como si acabara de escribir std :: string str;
David
44
@David: no estoy seguro de que se requiera que un objeto movido sea válido para cualquier uso, excepto la destrucción posterior.
Ben Voigt
46
vec.emplace_back("Hello")funcionará, ya que el const char*argumento será enviado al stringconstructor. Este es todo el punto de emplace_back.
Alexandre C.
8
@BenVoigt: se requiere que un objeto movido desde esté en un estado válido (pero no especificado). Sin embargo, esto no significa necesariamente que pueda realizar cualquier operación en él. Considere std::vector. Un vacío std::vectores un estado válido, pero no puede invocarlo front(). Esto significa que cualquier función que no tenga condiciones previas aún puede llamarse (y los destructores nunca pueden tener condiciones previas).
David Stone
97

La optimización para emplace_backse puede demostrar en el siguiente ejemplo.

Para el emplace_backconstructor A (int x_arg)se llamará. Y porque push_back A (int x_arg)se llama primero y luego move A (A &&rhs)se llama.

Por supuesto, el constructor tiene que estar marcado como explicit, pero para el ejemplo actual es bueno eliminar lo explícito.

#include <iostream>
#include <vector>
class A
{
public:
  A (int x_arg) : x (x_arg) { std::cout << "A (x_arg)\n"; }
  A () { x = 0; std::cout << "A ()\n"; }
  A (const A &rhs) noexcept { x = rhs.x; std::cout << "A (A &)\n"; }
  A (A &&rhs) noexcept { x = rhs.x; std::cout << "A (A &&)\n"; }

private:
  int x;
};

int main ()
{
  {
    std::vector<A> a;
    std::cout << "call emplace_back:\n";
    a.emplace_back (0);
  }
  {
    std::vector<A> a;
    std::cout << "call push_back:\n";
    a.push_back (1);
  }
  return 0;
}

salida:

call emplace_back:
A (x_arg)

call push_back:
A (x_arg)
A (A &&)
vadikrobot
fuente
21
1 para el ejemplo de código que demuestra lo que realmente sucede cuando se llama emplace_backvs push_back.
Shawn
Vine aquí después de notar que tenía un código que llamaba v.emplace_back(x);donde x es explícitamente construible por movimiento pero solo explícitamente construible por copia. El hecho de que emplace_backsea ​​"implícitamente" explícito me hace pensar que mi función de ir a anexar probablemente debería serlo push_back. Pensamientos?
Ben
Si llama por a.emplace_backsegunda vez, se llamará al constructor de movimiento.
X Æ A-12
8

emplace_backla implementación conforme reenviará argumentos al vector<Object>::value_typeconstructor cuando se agregue al vector. Recuerdo que Visual Studio no admitía plantillas variadas, pero con las plantillas variadas se admitirán en Visual Studio 2013 RC, por lo que supongo que se agregará una firma conforme.

Con emplace_back, si reenvía los argumentos directamente al vector<Object>::value_typeconstructor, no necesita un tipo para ser movible o copiable para la emplace_backfunción, estrictamente hablando. En el vector<NonCopyableNonMovableObject>caso, esto no es útil, ya que vector<Object>::value_type necesita un tipo copiable o móvil para crecer.

Pero tenga en cuenta que esto podría ser útil std::map<Key, NonCopyableNonMovableObject>, ya que una vez que asigna una entrada en el mapa, ya no es necesario moverla ni copiarla , a diferencia de vector, lo que significa que puede usarla std::mapefectivamente con un tipo mapeado que no sea copiable ni móvil.

Germán Diago
fuente
8

Uno más en caso de listas:

// constructs the elements in place.                                                
emplace_back("element");


//It will create new object and then copy(or move) its value of arguments.
push_back(explicitDataType{"element"});
HackSlash
fuente
1

Caso de uso específico para emplace_back: Si necesita crear un objeto temporal que luego será empujado a un contenedor, use en emplace_backlugar depush_back . Creará el objeto en el lugar dentro del contenedor.

Notas:

  1. push_backen el caso anterior creará un objeto temporal y lo moverá al contenedor. Sin embargo, la construcción en el lugar utilizada emplace_backsería más eficiente que construir y luego mover el objeto (lo que generalmente implica alguna copia).
  2. En general, puede usar en emplace_backlugar de push_backen todos los casos sin muchos problemas. (Ver excepciones )
vaibhav kumar
fuente