2010-11-29 13 views
469

Estoy un poco confundido con respecto a la diferencia entre push_back y emplace_back.push_back frente a emplace_back

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

Como hay una sobrecarga de push_back teniendo una referencia rvalue No acabo de ver lo que el propósito de emplace_back se convierte?

+11

Algunas buenas lecturas aquí: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2642.pdf –

+14

Tenga en cuenta que (como dice Thomas a continuación), el código en la pregunta es de la * emulación * de MSVS de C++ 0x, no lo que realmente es C++ 0x. – me22

+4

Un documento mejor para leer sería: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2345.pdf. N2642 es en su mayoría redacción para el estándar; N2345 es el documento que explica y motiva la idea. – Alan

Respuesta

384

Además de lo visitante dijo:

La función void emplace_back(Type&& _Val) proporcionada por MSCV10 es no conforme y redundante, ya que como se anotó, es estrictamente equivalente a push_back(Type&& _Val) .

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

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

Eso es útil, ya que no importa cuánta astucia RVO y semántica de movimientos traiga a la mesa todavía hay casos complicados en los que un push_back probablemente haga copias innecesarias (o se mueva). Por ejemplo, con el tradicional insert() función de un std::map, usted tiene que crear un temporal, que luego se copia en un std::pair<Key, Value>, que luego se copia 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); 

Así que por qué no lo hicieron implementar 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 Visual C++ blog. Aquí está la respuesta de Stephan T Lavavej, el mantenedor oficial de la implementación de la biblioteca estándar de Visual C++ en Microsoft.

P: ¿Las funciones de la versión beta 2 son solo algún tipo de marcador de posición en este momento?

A: Como ya sabe, las plantillas variadas no están implementadas en VC10. Nosotros simularlos con el preprocesador maquinaria para cosas como , tupla, y el nuevo cosas en <functional>. Esta maquinaria de preprocesador es relativamente difícil de usar y mantener.Además, afecta significativamente a la velocidad de compilación , ya que tenemos que repetidamente incluir subcabeceras. Debido a una combinación de de nuestras limitaciones de tiempo y problemas de velocidad de compilación, no hemos simulado las plantillas variadicas en nuestras funciones de despliegue.

Cuando las plantillas son variadic implementado en el compilador, puede esperar que vamos a tomar ventaja de ellos en las bibliotecas, incluyendo en nuestras funciones emplace. Tomamos la conformidad muy en serio, pero lamentablemente no podemos hacer todo todos a la vez.

Es una decisión comprensible. Todos los que intentaron emular una plantilla variadic con horribles trucos de preprocesador saben lo desagradable que es esto.

+63

Esa aclaración de que se trata de un problema MSVS10, no es un problema de C++ es la parte más importante aquí. Gracias. – me22

+9

Creo que su última línea de código C++ no funcionará. 'pair ' no tiene un constructor que tome un int, otro int, un double y como 4th parameter una cadena. Sin embargo, * puedes * construir directamente este objeto de par usando 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

+2

Felizmente, las plantillas variadas estarán en VS2013, ahora en vista previa. –

146

emplace_back no debería tomar un argumento del tipo vector::value_type, sino en cambio argumentos variados que se reenvían al constructor del elemento adjunto.

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

Es posible pasar un value_type que se transmitirá al constructor de copia.

Como reenvía los argumentos, esto significa que si no tiene el valor r, 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 al que hace push_back. Es probable que sea más bien pensado para 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 
+0

Debería usar vec.emplace_back (std :: move (s)); ya que s es un lvalue para obtener el comportamiento de movimiento – David

+2

@David: pero luego tienes un 's' movido en el alcance, ¿no es peligroso? –

+2

No es peligroso si no planeas usar s por su valor. Moverse no lo hace inválido, el movimiento solo robará la asignación de la memoria interna ya hecha en sy lo dejará en un estado predeterminado (no se asignará ninguna molestia) que cuando se destruya estará bien como si acabara de escribir std :: string str; – David

7

emplace_back conforming implementation reenviará argumentos al constructor vector<Object>::value_type cuando se agreguen al vector. Recuerdo que Visual Studio no admitió plantillas variadic, pero con Visual Studio 2013 RC se admitirán plantillas variad, así que supongo que se agregará una firma conforme.

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

Pero nota de que esto podría ser útil para std::map<Key, NonCopyableNonMovableObject>, ya que una vez que asigne una entrada en el mapa, que no necesita ser movido o copiado cada vez más, a diferencia de vector, lo que significa que se puede utilizar con eficacia std::map con un tipo mapeado que no se puede copiar ni mover.

25

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

Para emplace_back se llamará al constructor A (int x_arg). Y para push_backA (int x_arg) se llama primero y se llama después move A (A &&rhs).

Por supuesto el constructor tiene que marcarse explicit, pero para el ejemplo actual es bueno eliminar la explicitud.

#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 &&) 
2

Uno más en caso de listas:

// construye los elementos en su lugar.
emplace_back ("elemento");

// Creará un nuevo objeto y luego copiará (o moverá) su valor de argumentos. push_back (explicitDataType {"element"});

Cuestiones relacionadas