Хоча @Marc дав (що, на мою думку, є) чудовий аналіз, деякі люди можуть вважати за краще розглянути речі з дещо іншого кута.
Потрібно розглянути дещо інший спосіб перерозподілу. Замість того, щоб миттєво копіювати всі елементи зі старого сховища до нового сховища, розгляньте можливість копіювання лише одного елемента за один раз - тобто кожного разу, коли ви робите push_back, він додає новий елемент у новий простір та копіює рівно один існуючий елемент від старого простору до нового простору. Припускаючи коефіцієнт зростання 2, досить очевидно, що коли новий простір заповнений, ми б закінчили копіювати всі елементи зі старого простору в новий простір, і кожен push_back був точно постійним часом. У цей момент ми відкинемо старий простір, виділимо новий блок пам'яті, який був удвічі більшим посиленням, і повторимо процес.
Досить чітко, ми можемо продовжувати це нескінченно (або поки у вас є пам'ять у будь-якому разі), і кожен push_back передбачає додавання одного нового елемента та копіювання одного старого елемента.
Типова реалізація все ще має точно стільки ж копій, але замість того, щоб робити копії по черзі, вона копіює всі існуючі елементи одразу. З одного боку, ви праві: це означає, що якщо ви подивитесь на окремі виклики push_back, деякі з них будуть значно повільнішими, ніж інші. Якщо ми подивимось на середньострокове середнє значення, то кількість копіювання, виконаної за виклик push_back, залишається постійною, незалежно від розміру вектора.
Хоча це не має значення для складності обчислень, я думаю, що варто зазначити, чому вигідно робити такі речі, як вони, замість того, щоб копіювати один елемент на push_back, тому час на push_back залишається постійним. Існує щонайменше три причини.
Перший - просто наявність пам'яті. Стару пам'ять можна звільнити для іншого використання лише після закінчення копіювання. Якщо ви копіювали лише один елемент за один раз, старий блок пам'яті залишатиметься значно довше. Насправді у вас був би весь старий блок і один новий блок, по суті, весь час виділений. Якщо ви визначилися з коефіцієнтом росту, меншим за два (який зазвичай ви хочете), вам буде потрібно весь час виділяти більше пам'яті.
По-друге, якби ви скопіювали лише один старий елемент за один раз, індексування в масив було б трохи складніше - кожна операція індексації повинна була б розібратися, чи перебуває елемент у вказаного індексу в старому блоці пам'яті чи новий. Це не дуже страшно будь-яким способом, але для елементарної операції, такої як індексація в масив, практично будь-яке уповільнення може бути суттєвим.
По-третє, скопіювавши всі одразу, ви скористаєтеся значно кращою перевагою кешування. Скопіювавши все одразу, ви можете очікувати, що і джерело, і цільове місце у більшості випадків будуть у кеші, тому вартість пропуску кешу амортизується на кількість елементів, які вмістяться в рядок кешу. Якщо ви копіюєте один елемент одночасно, ви можете легко пропустити кеш-пам'ять для кожного елемента, який ви копіюєте. Це змінює лише постійний коефіцієнт, а не його складність, але він все ще може бути досить значним - для типової машини можна легко очікувати коефіцієнта від 10 до 20.
Напевно, варто також на мить розглянути інший напрямок: якщо ви проектували систему з вимогами в режимі реального часу, цілком може бути сенсом копіювати лише один елемент одночасно, а не всі одразу. Хоча загальна швидкість може (або не може бути) нижчою, у вас все ще буде чітка верхня межа часу, необхідного для одного виконання push_back - припускаючи, що у вас є розподільник у реальному часі (хоча, звичайно, багато в реальному часі системи просто забороняють динамічне розподіл пам’яті взагалі, принаймні, у частині, що потребує реального часу).