Obwohl @Marc eine ausgezeichnete Analyse geliefert hat (was ich denke), ziehen es manche Leute möglicherweise vor, die Dinge aus einem etwas anderen Blickwinkel zu betrachten.
Eine Möglichkeit besteht darin, eine etwas andere Art der Neuzuweisung in Betracht zu ziehen. Anstatt alle Elemente sofort aus dem alten Speicher in den neuen Speicher zu kopieren, sollten Sie immer nur ein Element gleichzeitig kopieren. Wenn Sie also ein push_back ausführen, wird das neue Element dem neuen Bereich hinzugefügt und genau ein vorhandenes kopiert Element aus dem alten Raum in den neuen Raum. Unter der Annahme eines Wachstumsfaktors von 2 ist es ziemlich offensichtlich, dass, wenn der neue Raum voll ist, wir alle Elemente aus dem alten Raum in den neuen Raum kopiert haben und jeder push_back genau eine konstante Zeit war. Zu diesem Zeitpunkt verwerfen wir den alten Speicherplatz, weisen einen neuen Speicherblock zu, der doppelt so groß ist, und wiederholen den Vorgang.
Ziemlich klar, wir können dies auf unbestimmte Zeit fortsetzen (oder so lange Speicher verfügbar ist) und jeder Push_back würde das Hinzufügen eines neuen Elements und das Kopieren eines alten Elements beinhalten.
Eine typische Implementierung hat immer noch genau die gleiche Anzahl an Kopien - aber anstatt die Kopien einzeln zu erstellen, werden alle vorhandenen Elemente auf einmal kopiert. Einerseits haben Sie Recht: Wenn Sie sich einzelne Aufrufe von push_back ansehen, sind einige wesentlich langsamer als andere. Wenn wir uns jedoch einen langfristigen Durchschnitt ansehen, bleibt der Kopieraufwand pro Aufruf von push_back konstant, unabhängig von der Größe des Vektors.
Obwohl es für die Komplexität der Berechnung irrelevant ist, ist es meiner Meinung nach erwähnenswert, warum es vorteilhaft ist, die Dinge so zu machen, wie sie sind, anstatt ein Element pro push_back zu kopieren, damit die Zeit pro push_back konstant bleibt. Es gibt mindestens drei Gründe zu berücksichtigen.
Das erste ist einfach die Speicherverfügbarkeit. Der alte Speicher kann erst nach Abschluss des Kopiervorgangs für andere Zwecke freigegeben werden. Wenn Sie jeweils nur ein Objekt kopieren, bleibt der alte Speicherblock viel länger reserviert. In der Tat würden Sie einen alten Block und einen neuen Block im Wesentlichen die ganze Zeit zugewiesen haben. Wenn Sie sich für einen Wachstumsfaktor entscheiden, der kleiner als zwei ist (was normalerweise gewünscht wird), benötigen Sie immer noch mehr zugewiesenen Speicher.
Zweitens, wenn Sie immer nur ein altes Element kopieren würden, wäre die Indizierung in das Array etwas komplizierter. Bei jeder Indizierungsoperation müsste herausgefunden werden, ob sich das Element am angegebenen Index derzeit im alten Speicherblock oder im Speicher befindet ein neues. Das ist keineswegs besonders komplex, aber für eine elementare Operation wie das Indizieren in ein Array kann fast jede Verlangsamung von Bedeutung sein.
Drittens können Sie durch gleichzeitiges Kopieren das Caching viel besser nutzen. Wenn Sie alles auf einmal kopieren, können Sie in den meisten Fällen davon ausgehen, dass sich sowohl die Quelle als auch das Ziel im Cache befinden. Die Kosten für einen Cache-Fehler werden daher über die Anzahl der Elemente, die in eine Cache-Zeile passen, amortisiert. Wenn Sie jeweils ein Element kopieren, kann es leicht vorkommen, dass für jedes Element, das Sie kopieren, ein Cache-Fehler auftritt. Das ändert nur den konstanten Faktor, nicht die Komplexität, kann aber dennoch ziemlich bedeutend sein - für eine typische Maschine kann man leicht einen Faktor von 10 bis 20 erwarten.
Es lohnt sich wahrscheinlich auch, einen Moment über die andere Richtung nachzudenken: Wenn Sie ein System mit Echtzeitanforderungen entwerfen, ist es möglicherweise sinnvoll, immer nur ein Element anstatt aller Elemente gleichzeitig zu kopieren. Obwohl die Gesamtgeschwindigkeit möglicherweise niedriger ist (oder auch nicht), ist die Zeit, die für eine einzelne Ausführung von push_back benötigt wird, nach wie vor stark begrenzt - vorausgesetzt, Sie haben einen Echtzeit-Allokator (obwohl natürlich viele in Echtzeit) Systeme verbieten lediglich die dynamische Zuweisung von Speicher, zumindest in Teilen mit Echtzeitanforderungen.