Сколько объектов типа T существует в std::vector сразу после резервирования памяти, если размер контейнера ...

Сколько объектов типа T существует в std::vector<T> сразу после резервирования памяти, если размер контейнера остаётся нулевым?

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

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

Существует ноль объектов типа T. Операция резервирования изменяет вместимость и выделяет сырое хранилище, но не создаёт элементы и не вызывает их конструкторы.

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

Разделение size и capacity появилось как основа эффективного динамического массива. Контейнер может заранее получить запас памяти, чтобы последующие добавления не требовали частых перевыделений и перемещений уже существующих элементов.

Такой подход особенно важен для типов с дорогим конструированием или перемещением: резервируется память заранее, но сами объекты создаются только тогда, когда они действительно добавляются в контейнер.

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

После резервирования памяти доступный объём хранения может быть больше нуля, но это не означает, что по этим адресам уже существуют объекты. У std::vector<T> количество существующих элементов определяется size, а не capacity.

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

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

reserve(n) при необходимости выделяет сырое хранилище, достаточное как минимум для n элементов. Для пустого вектора после reserve(n) значение размера остаётся нулевым, поэтому конструктор T не вызывается ни разу.

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

#include <vector> struct Widget { Widget() = default; }; int main() { std::vector<Widget> items; items.reserve(100); items.emplace_back(); // capacity() не меньше 100, size() равен 1 }

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

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

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

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

Вариант с resize создаёт все элементы немедленно. Он подходит, если каждая позиция должна существовать заранее и затем заполняться через присваивание, но может привести к лишним конструкциям и инициализациям.

Хранение сырого буфера с последующим ручным временем жизни объектов даёт больший контроль, однако требует самостоятельно соблюдать правила выравнивания, конструирования, уничтожения и обработки исключений. Поэтому для обычного динамического массива выбирают reserve вместе с emplace_back: результатом становится меньший объём ручного управления и корректное владение памятью.

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

  1. Вызывает ли reserve конструкторы элементов?

Нет, если речь идёт о новой вместимости без изменения размера. reserve может переместить уже существующие элементы при перевыделении, но не создаёт дополнительные элементы до значения capacity.

Для пустого вектора конструкторы элементов не вызываются вообще. Выделяется только хранилище, которым контейнер сможет воспользоваться позднее.

  1. Можно ли обращаться к элементу по индексу, если индекс меньше capacity, но не меньше size?

Нельзя. Проверка границ у operator[] концептуально относится к размеру контейнера, а не к его вместимости. За пределами size нет существующего элемента T, поэтому попытка чтения или записи через такой индекс имеет неопределённое поведение.

Если элементы должны существовать, нужно создать их через resize, push_back или emplace_back. Одна только предварительно выделенная память не превращается в объекты автоматически.

  1. Почему нельзя вручную создать объект прямо в области после reserve и считать его элементом вектора?

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

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