Что происходит с элементами контейнера при реаллокации (например, в std::vector)?

Ответ

При реаллокации (когда std::vector исчерпывает выделенную capacity) происходит следующее:

  1. Выделение новой памяти: Аллокатор выделяет новый, больший блок памяти (обычно в 1.5 или 2 раза больше предыдущего).
  2. Перенос элементов: Все существующие элементы переносятся из старой памяти в новую.
    • Если у типа элемента есть небросающий (noexcept) конструктор перемещения, он будет использован для эффективного переноса ресурсов.
    • В противном случае используется конструктор копирования.
  3. Уничтожение старых объектов: После успешного переноса все элементы в старой памяти уничтожаются (вызываются их деструкторы), и старая память освобождается.

Критические последствия:

  • Инвалидация итераторов и указателей: Все итераторы, указатели и ссылки на элементы вектора становятся невалидными.
  • Гарантия исключений: Если используется конструктор перемещения, объявленный как noexcept (или не бросающий исключений), реаллокация обеспечивает строгую гарантию безопасности исключений. Если перемещение может бросить исключение, используется копирование, что может привести к базовой гарантии (программа остаётся в корректном состоянии, но элементы могут быть утеряны).

Пример:

#include <vector>
#include <string>

int main() {
    std::vector<std::string> vec = {"first", "second"}; // capacity может быть 2
    vec.push_back("third"); // Триггер реаллокации
    // Элементы "first" и "second" перемещаются в новую память,
    // так как std::string имеет noexcept move constructor.
    // Старая память освобождается.
    return 0;
}

Совет: Используйте reserve() для предварительного выделения памяти, если известно примерное количество элементов, чтобы избежать множественных реаллокаций.

Ответ 18+ 🔞

Давай разберём эту тему, как будто на дворе 2002-й год и мы сидим в курилке после пары по алгоритмам. Слушай, вот представь себе — у тебя есть std::vector. Он как тачка, которая жрёт бензина дохуя, если ты постоянно в неё что-то пихаешь. Так вот, реаллокация — это когда твоя тачка уже не влезает в гараж, и тебе приходится строить новый, побольше.

Что происходит, когда вектору тесно:

  1. Новый гараж: Аллокатор, этот хитрая жопа, бежит и выделяет новый, больший кусок памяти. Обычно в полтора или два раза больше предыдущего. Овердохуища, да?
  2. Переезд: Все твои элементы, которые жили в старом гараже, теперь надо перетащить в новый.
    • Если у твоего типа есть небросающий (noexcept) конструктор перемещения — то это как переехать на грузовике: быстро, чётко, ресурсы не копируются, а просто переезжают. Эффективно, ёпта.
    • А если нет — то начинается пиздопроебибна: всё копируется вручную, как будто ты таскаешь свои вещи на себе в десять заходов. Медленно и печально.
  3. Снос старого: После успешного переезда старый гараж со всеми своими призраками (деструкторами старых объектов) сносится к хуям, и память освобождается.

А теперь самое сокровенное, где можно обжечься:

  • Инвалидация всего и вся: Все итераторы, указатели и ссылки на элементы, которые ты так бережно хранил, становятся хуем с горы. Они указывают на старый, уже несуществующий гараж. Попробуй через них что-то прочитать — получишь неопределённое поведение, то есть, проще говоря, всё накроется медным тазом.
  • Гарантии, блядь: Если перемещение noexcept — то реаллокация даёт строгую гарантию: либо всё переехало идеально, либо ничего не тронуто, если что-то пошло не так. Если же перемещение может кинуть исключение — то используется копирование. Программа не упадёт (базовая гарантия), но часть твоих данных может просто потеряться в процессе. Доверия ебать ноль в таком случае.

Вот тебе наглядный пример, чтобы не быть голословным:

#include <vector>
#include <string>

int main() {
    std::vector<std::string> vec = {"first", "second"}; // В гараже места на две тачки
    vec.push_back("third"); // А ты пытаешься впихнуть третью! Ёперный театр, реаллокация!
    // Строки "first" и "second" теперь перемещаются на новое место,
    // потому что у std::string конструктор перемещения — огонь, `noexcept`.
    // Старый гараж идёт под снос.
    return 0;
}

Совет от бывалого: Если знаешь, сколько примерно элементов будет — используй reserve(). Это как сразу построить гараж на сто машин, даже если у тебя пока только две. Избавишь себя от кучи ненужных переездов и головной боли. Экономия нервов — овердохуища.