В чем проблема при вставке 100 элементов в конец std::vector без подготовки?

Ответ

Основная проблема — множественные реаллокации, которые приводят к падению производительности.

Механизм работы std::vector:

  1. Вектор хранит элементы в непрерывном блоке памяти.
  2. У него есть два размера: size() (количество элементов) и capacity() (размер выделенного блока памяти).
  3. При push_back(), если size() == capacity(), происходит реаллокация: выделяется новый, больший блок памяти (обычно в 1.5 или 2 раза больше), все существующие элементы копируются или перемещаются в него, старый блок освобождается.

Пример с проблемой:

std::vector<int> vec; // capacity = 0
for (int i = 0; i < 100; ++i) {
    vec.push_back(i); // Реаллокации произойдут при i = 0, 1, 2, 4, 8, 16, 32, 64...
}
// Каждая реаллокация — это O(N) операций копирования/перемещения.

Решение — метод reserve():

std::vector<int> vec;
vec.reserve(100); // Однократное выделение памяти под 100 элементов
for (int i = 0; i < 100; ++i) {
    vec.push_back(i); // Ни одной реаллокации. Вставка за O(1).
}

Дополнительные соображения:

  • Использование emplace_back() вместо push_back() позволяет конструировать объекты прямо в памяти вектора, избегая лишних копирований.
  • Если конечный размер известен, можно использовать конструктор или assign().
    // Альтернатива: инициализация из итераторов или списка
    std::vector<int> vec(100); // 100 элементов, инициализированных нулями
    // или
    std::vector<int> vec;
    vec.assign(100, 0); // 100 нулей

Ответ 18+ 🔞

А, слушай, вот это реально больная тема, которая у новичков постоянно вылезает. Представь себе, что ты пытаешься впихнуть диван в квартиру, а дверной проём — узкий. Ты его туда, блядь, засовываешь, потом понимаешь — не лезет. Вытаскиваешь, ломаешь стену, делаешь проём шире, затаскиваешь диван. Потом привозят кресло — и опять та же история! Это и есть множественные реаллокации, ёпта. Производительность на них просто накрывается медным тазом.

Как этот std::vector изнутри устроен, блядь:

  1. Он, хитрая жопа, хранит всё в одной куче памяти, подряд.
  2. У него есть два параметра: size() — сколько элементов уже внутри лежит, и capacity() — а сколько вообще влезет в тот кусок памяти, который он уже отгрыз у системы.
  3. Вот ты делаешь push_back(). Если места (capacity) ещё хватает — всё ок, пихаем. А если нет? Ёперный театр! Начинается реаллокация: он бежит, ищет новый, больший кусок памяти (обычно в 1.5 или 2 раза больше), потом тупо копирует или перемещает туда все старые элементы, а старый кусок выкидывает. И так каждый раз, когда упрётся.

Смотри, как это выглядит в коде, просто пиздец:

std::vector<int> vec; // capacity = 0, места нет нихуя
for (int i = 0; i < 100; ++i) {
    vec.push_back(i); // Реаллокации будут при i = 0, 1, 2, 4, 8, 16, 32, 64...
}
// Каждая такая операция — это O(N), то есть чем больше накопилось, тем дольше всё копировать. Ужас, да?

Решение? Да элементарно, Ватсон! Метод reserve():

std::vector<int> vec;
vec.reserve(100); // Всё, блядь! Одним махом говорим системе: "Дай мне памяти на сотню интов, не ссы!"
for (int i = 0; i < 100; ++i) {
    vec.push_back(i); // И тут уже нихуя не реаллоцируется! Вставка за O(1), красота.
}

Вот и вся магия. Сразу видно, кто в теме писал код, а кто нет. Доверия ебать ноль к тому, кто reserve не использует, когда размер известен.

Ещё пара советов, чтобы вообще охуенно было:

  • Вместо push_back() можно emplace_back() юзать. Он объект прямо на месте, в памяти вектора, конструирует, минуя лишние копирования. Умно, блядь.
  • Если уж совсем точно знаешь, что будет, можно сразу конструктором или assign().
    // Вариант раз: сразу сто нулей
    std::vector<int> vec(100);
    // Вариант два: команда "сделай мне сто нулей"
    std::vector<int> vec;
    vec.assign(100, 0);

Короче, суть в чём: не заставляй вектор каждый раз переезжать на новую квартиру. Скажи ему сразу, сколько тебе нужно комнат, и живи спокойно. Волнение ебать отпадает сразу.