c++ - push_back vs emplace_back
visual-studio-2010 stl (6)
Estoy un poco confundido con respecto a la diferencia entre push_back
y emplace_back
.
void emplace_back(Type&& _Val);
void push_back(const Type& _Val);
void push_back(Type&& _Val);
Como hay una sobrecarga push_back
toma una referencia rvalue, ¿no veo cuál es el propósito de emplace_back
?
Además de lo que dijo el visitante:
La función void emplace_back(Type&& _Val)
provista por MSCV10 no es conforme y es redundante, ya que, como se indicó, es estrictamente equivalente a push_back(Type&& _Val)
.
Pero la forma real de C ++ 0x de emplace_back
es 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 de temporal.
Eso es útil porque no importa cuán inteligente sea el RVO y el movimiento semántico en la mesa, todavía hay casos complicados en los que es probable que un push_back haga copias innecesarias (o se mueva). Por ejemplo, con la función insert()
tradicional de un std::map
, tiene que crear un temporal, que luego se copiará en un 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, me molestó también 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: ¿Son las funciones de emplace beta 2 solo un tipo de marcador de posición en este momento?
R: Como usted sabe, las plantillas variadic no están implementadas en VC10. Los simulamos con la maquinaria del preprocesador para cosas como
make_shared<T>()
, tuple y las cosas nuevas en<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 subtítulos repetidamente. Debido a una combinación de nuestras limitaciones de tiempo y problemas de velocidad de compilación, no hemos simulado varias plantillas en nuestras funciones emplace.Cuando se implementan plantillas variadic en el compilador, puede esperar que las aprovechemos en las bibliotecas, incluidas nuestras funciones emplace. 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 sola vez una plantilla variada con trucos horribles de preprocesador saben cuán repugnante se ponen estas cosas.
Aquí se muestra un buen código para push_back y emplace_back.
http://en.cppreference.com/w/cpp/container/vector/emplace_back
Puede ver la operación de movimiento en push_back y no en emplace_back.
La optimización para emplace_back
se puede demostrar en el siguiente ejemplo.
Para emplace_back
constructor A (int x_arg)
. Y para push_back
se llama primero a A (int x_arg)
se llama a move A (A &&rhs)
.
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 &&)
Una 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 {"elemento"});
emplace_back
implementación conforme con emplace_back
reenviará argumentos al vector<Object>::value_type
constructor cuando se agregue al vector. Recuerdo que Visual Studio no admitía las plantillas variadas, pero con las plantillas variadic se admitirán en Visual Studio 2013 RC, así que supongo que se agregará una firma conforme.
Con emplace_back
, si reenvía los argumentos directamente al vector<Object>::value_type
constructor, no necesita que un tipo sea movible o que pueda copiarse para la función emplace_back
, estrictamente hablando. En el caso del vector<NonCopyableNonMovableObject>
, esto no es útil, ya que vector<Object>::value_type
necesita un tipo copiable o movible para crecer.
Pero tenga en cuenta que esto podría ser útil para std::map<Key, NonCopyableNonMovableObject>
, ya que una vez que asigna una entrada en el mapa, no es necesario std::map<Key, NonCopyableNonMovableObject>
ni copiarlo nunca más, a diferencia de vector
, lo que significa que puede usar std::map
mapea efectivamente con un tipo mapeado que no se puede copiar ni mover.
emplace_back
no debería tomar un argumento de tipo vector::value_type
, sino que en lugar de argumentos variadic 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 reenviará al constructor de 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 debería ser idéntico a lo que hace push_back
. Probablemente es más bien destinado 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