Как меняются size и capacity std::vector после вызова clear и почему это важно при повторном заполнении?

Как меняются size и capacity std::vector после вызова clear() и почему это важно при повторном заполнении?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

После clear() размер std::vector становится равен нулю, но его capacity не уменьшается. Выделенная память обычно сохраняется, поэтому последующее заполнение может происходить без новых аллокаций, пока число элементов не превысит прежнюю вместимость.

Исторический контекст

std::vector проектировался как динамический массив с разделением двух понятий: количества фактически созданных элементов и объёма зарезервированного хранилища. Это позволяет удалить элементы, сохранив память для повторного использования, вместо обязательного освобождения и последующего выделения памяти.

Такой подход особенно полезен в циклических или пакетных задачах, где контейнер многократно очищается и заполняется объектами сопоставимого размера.

Постановка проблемы

Ошибочно считать, что clear() возвращает память операционной системе или эквивалентен созданию нового пустого вектора. Это может привести к неправильной оценке производительности и к неожиданно большому удержанию памяти, если вектор временно разросся.

После clear() элементы уничтожаются, поэтому ссылки, указатели и итераторы на них использовать нельзя. Старый итератор конца диапазона также нельзя считать действительным после операции.

Подробное решение

size() показывает число живых элементов, а capacity() — количество элементов, для которых уже выделено место. Метод clear() уничтожает все объекты в диапазоне, но не обязан освобождать буфер и не уменьшает capacity().

#include <cassert> #include <vector> int main() { std::vector<int> values; values.reserve(100); values.push_back(1); values.push_back(2); const auto saved_capacity = values.capacity(); values.clear(); assert(values.size() == 0); assert(values.capacity() == saved_capacity); }

После очистки вектор остаётся пригодным для повторного использования: новые элементы конструируются в уже выделенной памяти. Однако это не означает, что адреса прежних элементов можно использовать: сами объекты уничтожены, а любые операции, способные вызвать реаллокацию, могут сделать недействительными ранее полученные адреса.

Если память нужно попытаться освободить, применяют shrink_to_fit(), но это лишь необязательная рекомендация реализации. Надёжнее гарантированно освободить буфер через обмен с временным пустым вектором, если сохранение ёмкости не требуется.

Ситуация из практики

Сервис обрабатывает пакеты данных: перед обработкой каждого пакета вектор очищается, затем заполняется заново. Вариант с clear() сохраняет буфер и избегает повторных аллокаций, что уменьшает накладные расходы и фрагментацию памяти.

Альтернатива — создавать новый вектор для каждого пакета. Она проще с точки зрения времени жизни, но может приводить к частым выделениям памяти. Вызов shrink_to_fit() после каждого пакета также нежелателен: он может вызвать перераспределение памяти и свести преимущество повторного использования на нет.

Выбор clear() оправдан, если рабочий объём примерно стабилен. Если вектор однажды обработал огромный пакет и затем долго хранится с большой незадействованной ёмкостью, стоит отдельно рассмотреть освобождение памяти после завершения пикового этапа.

Что кандидаты часто упускают

  1. Гарантирует ли clear() освобождение памяти?

    Нет. Он уничтожает элементы и устанавливает размер в ноль, но capacity() не уменьшается. Освобождение буфера не является частью гарантии clear().

  2. Можно ли использовать указатель на элемент после clear(), если вектор не перевыделял память?

    Нет. Проблема не только в адресе памяти, но и во времени жизни объекта: clear() уничтожает все элементы. Обращение через старый указатель, ссылку или итератор не даёт действительного объекта и приводит к неопределённому поведению.

  3. Чем отличаются clear(), resize(0) и shrink_to_fit()?

    clear() уничтожает все элементы и оставляет вместимость без уменьшения. resize(0) также удаляет элементы до нулевого размера и обычно сохраняет вместимость; его главное назначение — изменение размера до указанного значения. shrink_to_fit() просит уменьшить неиспользуемую ёмкость, но реализация может проигнорировать этот запрос; сам по себе он не удаляет элементы.