Różnica insert i push_back wektora C ++

79

Chcę wiedzieć, jakie są różnice (s) pomiędzy vector„s push_backi insertfunkcji.

Czy są jakieś różnice strukturalne?

Czy jest naprawdę duża różnica (y) w wydajności?

totten
źródło
4
insertmożna to zrobić w dowolnym miejscu i obejmuje inne rzeczy, takie jak obsługa zakresów. push_backjest wygodniejszy do dodania na końcu.
chris

Odpowiedzi:

93

Największą różnicą jest ich funkcjonalność. push_backzawsze umieszcza nowy element na końcu vectori insertpozwala wybrać pozycję nowego elementu. Ma to wpływ na wydajność. vectorelementy są przenoszone w pamięci tylko wtedy, gdy konieczne jest zwiększenie jej długości, ponieważ przydzielono im zbyt mało pamięci. Z drugiej strony insertwymusza przesunięcie wszystkich elementów po wybranym położeniu nowego elementu. Po prostu musisz zrobić dla niego miejsce. Dlatego insertczęsto może być mniej wydajny niż push_back.

Adam Sznajder
źródło
9
Z drugiej strony wstawianie na końcu powinno być tak samo wydajne jak push_back.
Matthieu M.
18
Cóż, prawie tak samo wydajne - kod musi określić, że insertfaktycznie znajduje się w end(), więc będzie więcej gałęzi w insertniż w push_back, chyba że kompilator zorientuje się, że w rzeczywistości nie są one potrzebne.
Yakk - Adam Nevraumont
5
@TomerW Ludzie wybierają C lub C ++, aby móc sqeeze każdy pojedynczy cykl maszyny, jaki tylko mogą, aby zwiększyć wydajność, do tego stopnia, że ​​czasami pisali bezpośrednio w ASM ... każde "jeśli" się liczy, to nie jest java ..
Cesar
1
@Cesar To nie jest powód, dla którego wybierasz język ... cpp może być szybszy niż c i na odwrót ... do diabła, w niektórych przypadkach nawet java może być szybsza niż c w zakresie optymalizacji środowiska wykonawczego.
Tomer W,
@TomerW ho, więc dlaczego? oświeć mnie proszę.
Cesar
32

Funkcje mają różne cele. vector::insertpozwala wstawić obiekt w określonej pozycji w vector, a vector::push_backpo prostu przyklei obiekt na końcu. Zobacz poniższy przykład:

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}

Możesz użyć insertdo wykonania tej samej pracy, co push_backz v.insert(v.end(), value).

Joseph Mansfield
źródło
9

Oprócz tego, że push_back(x)robi to samo co insert(x, end())(może z nieco lepszą wydajnością), jest kilka ważnych rzeczy, które należy wiedzieć o tych funkcjach:

  1. push_backistnieje tylko na BackInsertionSequencekontenerach - więc na przykład nie istnieje na set. Nie mógł, ponieważ push_back()gwarantuje, że na końcu zawsze doda.
  2. Niektóre pojemniki również mogą zadowolić FrontInsertionSequencei mają push_front. Jest to spełnione przez deque, ale nie przez vector.
  3. Wartość insert(x, ITERATOR)pochodzi z InsertionSequence, która jest wspólna dla seti vector. W ten sposób możesz użyć jednego setlub vectorjako celu dla wielu wstawek. Jednak setposiada dodatkowo insert(x), co robi praktycznie to samo (to pierwsza wkładka w setdrodze jedynie przyspieszyć poszukiwania odpowiedniego miejsca zaczynając od innego iteratora - funkcja nie stosuje się w tym przypadku).

Zwróć uwagę na ostatni przypadek, że jeśli zamierzasz dodać elementy w pętli, robisz container.push_back(x)i container.insert(x, container.end())zrobisz skutecznie to samo. Jednak nie będzie to prawdą, jeśli otrzymasz to container.end()najpierw, a następnie użyjesz go w całej pętli.

Na przykład możesz zaryzykować następujący kod:

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

Spowoduje to efektywne skopiowanie całości ado vwektora, w odwrotnej kolejności i tylko wtedy, gdy masz szczęście, że wektor nie zostanie ponownie przydzielony do rozszerzenia (możesz temu zapobiec, dzwoniąc reserve()najpierw); jeśli nie masz tyle szczęścia, otrzymasz tak zwane UndefinedBehavior (tm). Teoretycznie nie jest to dozwolone, ponieważ iteratory wektora są uważane za unieważnione za każdym razem, gdy dodawany jest nowy element.

Jeśli zrobisz to w ten sposób:

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

będzie kopiować ana końcu vw pierwotnej kolejności i nie grozi to unieważnieniem iteratora.

[EDYTUJ] Wcześniej ten kod wyglądał w ten sposób i był to błąd, ponieważ inserterfaktycznie utrzymuje poprawność i zaawansowanie iteratora:

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

Więc ten kod również doda wszystkie elementy w oryginalnej kolejności bez żadnego ryzyka.

Ethouris
źródło
Twoje uwagi o std :: inserter jako „ryzykownym” dla kontenerów wektorowych są niepoprawne. W rzeczywistości ma na celu wyeliminowanie tych zagrożeń. Bez odwrotnej kolejności, bez niezdefiniowanego zachowania. Zobacz np. Fluentcpp.com/2017/10/06/stl-inserter-iterators-work, aby zapoznać się z podziałem.
downhillFromHere
Masz rację. Chciałem tylko skrócić kod, zamiast rozszerzać go w obszerną pętlę ręczną, więc tak naprawdę nie jest to prawdą, ponieważ inserterutrzymuje zaawansowanie i ważność iteratora. Będę musiał edytować post :)
Ethouris