Unterschied zwischen Einfügen und Zurückschieben des C ++ - Vektors

79

Ich will wissen , was Unterschied (n) zwischen vector‚s push_backund insertFunktionen.

Gibt es einen strukturellen Unterschied?

Gibt es einen wirklich großen Leistungsunterschied?

totten
quelle
4
insertkann es überall tun und beinhaltet andere Dinge wie die Unterstützung von Bereichen. push_backist bequemer zum Hinzufügen am Ende.
Chris

Antworten:

93

Der größte Unterschied ist ihre Funktionalität. push_backsetzt immer ein neues Element am Ende des vectorund insertermöglicht es Ihnen, die Position des neuen Elements auszuwählen. Dies wirkt sich auf die Leistung aus. vectorElemente werden nur dann in den Speicher verschoben, wenn die Länge erhöht werden muss, da zu wenig Speicher zugewiesen wurde. Auf der anderen Seite werden insertalle Elemente nach der ausgewählten Position eines neuen Elements verschoben. Sie müssen einfach einen Platz dafür schaffen. Dies ist der Grund, warum dies insertoft weniger effizient ist als push_back.

Adam Sznajder
quelle
9
Andererseits sollte das Einsetzen am Ende so effizient sein wie push_back.
Matthieu M.
18
Nun, fast genauso effizient - der Code muss feststellen, dass sich das inserttatsächlich im befindet end(), sodass es mehr Zweige insertals in geben wird push_back, es sei denn, der Compiler kann herausfinden, dass sie tatsächlich nicht benötigt werden.
Yakk - Adam Nevraumont
5
@TomerW Die Leute wählen C oder C ++, um jeden einzelnen Maschinenzyklus, den sie können, um die Leistung zu steigern, bis zu dem Punkt, an dem sie manchmal direkt in ASM schreiben können ... jedes "Wenn" zählt, ist dies nicht Java.
Cesar
1
@Cesar Es ist nicht der Grund, warum Sie Ihre Sprache wählen ... cpp kann schneller sein als c und umgekehrt ... Hölle, in einigen Fällen kann sogar Java schneller sein als c für seine Laufzeitoptimierungen.
Tomer W
@TomerW ho, also warum? Erleuchte mich bitte.
Cesar
32

Die Funktionen haben unterschiedliche Zwecke. vector::insertMit dieser Option können Sie ein Objekt an einer bestimmten Position in das Objekt einfügen vector, während vector::push_backdas Objekt nur am Ende angebracht wird. Siehe folgendes Beispiel:

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}

Sie können insertden gleichen Job wie push_backmit ausführen v.insert(v.end(), value).

Joseph Mansfield
quelle
9

Neben der Tatsache, dass push_back(x)dies dasselbe tut wie insert(x, end())(möglicherweise mit etwas besserer Leistung), gibt es einige wichtige Dinge, die Sie über diese Funktionen wissen sollten:

  1. push_backexistiert nur auf BackInsertionSequenceContainern - so existiert es beispielsweise nicht auf set. Es konnte nicht, weil es push_back()Ihnen gewährt, dass es immer am Ende hinzugefügt wird.
  2. Einige Behälter können auch befriedigen FrontInsertionSequenceund sie haben push_front. Dies wird von erfüllt deque, aber nicht von vector.
  3. Das insert(x, ITERATOR)ist von InsertionSequence, was für setund gemeinsam ist vector. Auf diese Weise können Sie entweder setoder vectorals Ziel für mehrere Einfügungen verwenden. Hat setjedoch zusätzlich insert(x), was praktisch das Gleiche tut (dieses erste Einfügen setbedeutet nur, die Suche nach einem geeigneten Ort zu beschleunigen, indem von einem anderen Iterator ausgegangen wird - eine Funktion, die in diesem Fall nicht verwendet wird).

Beachten Sie im letzten Fall, dass Sie, wenn Sie Elemente in die Schleife einfügen möchten, dasselbe tun container.push_back(x)und container.insert(x, container.end())effektiv tun werden. Dies ist jedoch nicht der Fall, wenn Sie dies container.end()zuerst erhalten und dann in der gesamten Schleife verwenden.

Sie könnten beispielsweise den folgenden Code riskieren :

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

Dadurch wird das Ganze effektiv in umgekehrter Reihenfolgea in den vVektor kopiert , und zwar nur dann, wenn Sie das Glück haben, den Vektor nicht für die Erweiterung neu zuzuweisen (Sie können dies verhindern, indem Sie zuerst aufrufen ). Wenn Sie nicht so viel Glück haben, erhalten Sie das sogenannte UndefinedBehavior (tm). Theoretisch ist dies nicht zulässig, da die Iteratoren des Vektors jedes Mal als ungültig betrachtet werden, wenn ein neues Element hinzugefügt wird.reserve()

Wenn Sie es so machen:

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

Es wird aam Ende vin der ursprünglichen Reihenfolge kopiert , und dies birgt nicht das Risiko einer Iterator-Ungültigkeit.

[BEARBEITEN] Ich habe diesen Code zuvor so aussehen lassen, und es war ein Fehler, weil inserterdie Gültigkeit und Weiterentwicklung des Iterators tatsächlich erhalten bleibt:

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

Dieser Code fügt also auch alle Elemente in der ursprünglichen Reihenfolge ohne Risiko hinzu.

Ethouris
quelle
Ihre Bemerkungen, dass std :: inserter für vektorähnliche Container "riskant" sei, sind falsch. Tatsächlich soll es diese Risiken beseitigen. Keine umgekehrte Reihenfolge, kein undefiniertes Verhalten. Eine Aufschlüsselung finden Sie beispielsweise unter fluentcpp.com/2017/10/06/stl-inserter-iterators-work .
bergab von hier
Du hast recht. Ich wollte den Code nur kurz machen, anstatt ihn zu einer umfangreichen manuellen Schleife zu erweitern, daher ist dies eigentlich nicht der Fall, da inserterder Fortschritt und die Gültigkeit des Iterators erhalten bleiben. Ich muss den Beitrag bearbeiten :)
Ethouris