Diferencia de inserción y retroceso del vector C ++

79

Quiero saber cuáles son las diferencias entre vectorlas funciones push_backy insert.

¿Existe alguna diferencia estructural?

¿Existe una diferencia de rendimiento realmente grande?

Totten
fuente
4
insertpuede hacerlo en cualquier lugar e incluye otras cosas como soporte de rangos. push_backes más conveniente para agregar al final.
chris

Respuestas:

93

La mayor diferencia es su funcionalidad. push_backsiempre pone un nuevo elemento al final del vectory le insertpermite seleccionar la posición del nuevo elemento. Esto impacta el desempeño. vectorlos elementos se mueven en la memoria solo cuando es necesario aumentar su longitud porque se le asignó muy poca memoria. Por otro lado insertobliga a mover todos los elementos después de la posición seleccionada de un nuevo elemento. Simplemente tienes que hacerle un lugar. Es por eso insertque a menudo puede ser menos eficiente que push_back.

Adam Sznajder
fuente
9
Por otro lado, insertar al final debería ser tan eficiente como push_back.
Matthieu M.
18
Bueno, casi tan eficiente: el código necesita determinar que el insertestá realmente en el end(), por lo que habrá más ramas adentro insertque adentro push_back, a menos que el compilador pueda darse cuenta de que en realidad no son necesarias.
Yakk - Adam Nevraumont
5
@TomerW La gente elige C o C ++ para poder cuadrar cada ciclo de máquina que puedan, para aumentar el rendimiento, hasta el punto de escribir a veces directamente en ASM ... cada "si" cuenta, esto no es Java ..
Cesar
1
@Cesar No es por qué eliges tu idioma ... cpp puede ser más rápido que c, y viceversa ... demonios, en algunos casos incluso java puede ser más rápido que c para sus optimizaciones de tiempo de ejecución.
Tomer W
@TomerW ho, entonces ¿por qué? iluminame por favor.
Cesar
32

Las funciones tienen diferentes propósitos. vector::insertle permite insertar un objeto en una posición específica en el vector, mientras vector::push_backque simplemente pegará el objeto al final. Vea el siguiente ejemplo:

using namespace std;
vector<int> v = {1, 3, 4};
v.insert(next(begin(v)), 2);
v.push_back(5);
// v now contains {1, 2, 3, 4, 5}

Puede utilizar insertpara realizar el mismo trabajo que push_backcon v.insert(v.end(), value).

Joseph Mansfield
fuente
9

Además del hecho, que push_back(x)hace lo mismo que insert(x, end())(quizás con un rendimiento ligeramente mejor), hay varias cosas importantes que debe saber sobre estas funciones:

  1. push_backsolo existe en BackInsertionSequencecontenedores, por lo que, por ejemplo, no existe en set. No podría porque push_back()te garantiza que siempre se agregará al final.
  2. Algunos envases también pueden satisfacer FrontInsertionSequencey lo han hecho push_front. Esto es satisfecho por deque, pero no por vector.
  3. El insert(x, ITERATOR)es de InsertionSequence, que es común para sety vector. De esta forma, puede utilizar seto vectorcomo destino para múltiples inserciones. Sin embargo, settiene además insert(x), que hace prácticamente lo mismo (esta primera inserción setsignifica solo para acelerar la búsqueda del lugar apropiado comenzando desde un iterador diferente, una característica que no se usa en este caso).

Tenga en cuenta sobre el último caso que si va a agregar elementos en el ciclo, entonces hacer container.push_back(x)y container.insert(x, container.end())hará efectivamente lo mismo. Sin embargo, esto no será cierto si obtiene esto container.end()primero y luego lo usa en todo el ciclo.

Por ejemplo, podría arriesgarse al siguiente código:

auto pe = v.end();
for (auto& s: a)
    v.insert(pe, v);

Esto efectivamente copiará todo aal vvector, en orden inverso , y solo si tiene la suerte de no reasignar el vector para la extensión (puede evitarlo llamando reserve()primero); si no tiene tanta suerte, obtendrá el llamado UndefinedBehavior (tm). En teoría, esto no está permitido porque los iteradores del vector se consideran invalidados cada vez que se agrega un nuevo elemento.

Si lo haces de esta manera:

copy(a.begin(), a.end(), back_inserter(v);

se copiará aal final de ven el orden original, y esto no conlleva el riesgo de invalidación del iterador.

[EDITAR] Hice anteriormente que este código se viera de esta manera, y fue un error porque en inserterrealidad mantiene la validez y el avance del iterador:

copy(a.begin(), a.end(), inserter(v, v.end());

Entonces, este código también agregará todos los elementos en el orden original sin ningún riesgo.

Ethouris
fuente
Sus comentarios acerca de que std :: inserter es "arriesgado" para contenedores de tipo vectorial son incorrectos. De hecho, está diseñado para eliminar estos riesgos. Sin orden inverso, sin comportamiento indefinido. Consulte, por ejemplo, fluentcpp.com/2017/10/06/stl-inserter-iterators-work para obtener un desglose.
cuesta abajo desde aquí
Tienes razón. Solo quería acortar el código en lugar de expandirlo en un bucle manual extenso, por lo que en realidad esto no es cierto porque insertermantiene el avance y la validez del iterador. Tendré que editar la publicación :)
Ethouris