В практической задаче алгоритм должен последовательно добавлять результаты в пустой контейнер. Какой механизм выходного итератора позволяет алгоритму автоматически увеличивать размер контейнера?
Используйте вставляющий итератор, обычно std::back_inserter. Он превращает операцию записи алгоритма в вызов push_back, поэтому элементы добавляются в контейнер, а его размер увеличивается автоматически.
Обычный итератор, полученный через begin(), только перезаписывает уже существующие элементы и не изменяет размер контейнера.
STL разделяет контейнеры и алгоритмы: алгоритм работает с диапазонами итераторов и не обязан знать внутреннее устройство контейнера. Это позволяет применять один алгоритм к разным структурам данных.
Однако запись через обычный итератор предполагает, что место назначения уже существует. Для согласования алгоритмов с операциями вставки были введены специальные адаптеры итераторов, которые преобразуют присваивание в действие контейнера.
Если контейнер пуст, его begin() не указывает на элемент, в который можно записать результат. Попытка копировать данные в такой диапазон приводит к неопределённому поведению, поскольку алгоритм разыменовывает недействительный выходной итератор.
Вызов reserve также не решает проблему: он выделяет память, но не создаёт элементы и не увеличивает size(). Неверный выбор выходного итератора может привести к повреждению памяти или к ошибке выполнения.
std::back_inserter(container) возвращает объект типа std::back_insert_iterator. При операции записи этот адаптер вызывает у контейнера push_back, поэтому каждый результат алгоритма добавляется в конец.
Внутренне алгоритм выполняет запись в выходной итератор, а адаптер преобразует её в добавление элемента. Поэтому заранее задавать размер destination не требуется.
Для std::vector, std::deque и std::list подходит back_inserter, поскольку эти контейнеры поддерживают push_back. Для добавления в начало применяется std::front_inserter, а для вставки перед определённой позицией — std::inserter.
У вставки есть цена: при расширении std::vector возможны перераспределения памяти. Если примерный объём результата известен, перед использованием back_inserter можно вызвать reserve; это уменьшит число перераспределений, но не заменяет сам вставляющий итератор.
Сервис получает набор элементов и формирует новый вектор результатов неизвестного заранее размера. Вариант с записью через destination.begin() некорректен для пустого вектора, а предварительное создание большого числа элементов приводит к лишним значениям и необходимости отдельно отслеживать фактический размер.
Можно использовать push_back в ручном цикле: это прозрачно, но дублирует логику обхода и хуже сочетается с готовыми алгоритмами. Можно применить insert с диапазоном: это эффективно, если весь диапазон уже известен, но менее универсально для алгоритмов, которые генерируют результаты по одному.
Выбранный вариант — алгоритм с std::back_inserter и предварительным reserve, если оценка размера доступна. Он сохраняет обобщённость алгоритма, корректно меняет размер контейнера и обычно сокращает количество перераспределений.
Вопрос: гарантирует ли reserve возможность записи элементов через begin() до изменения размера?
Нет. reserve изменяет только capacity(), но не size(). После резервирования у вектора по-прежнему нет новых элементов, поэтому диапазон [begin(), end()) остаётся пустым. Записывать через begin() можно только в уже существующие элементы; для добавления нужен back_inserter либо явное изменение размера через resize.
Вопрос: может ли перераспределение std::vector сломать работу back_inserter?
Обычно нет. back_insert_iterator хранит ссылку или указатель на сам контейнер, а не итератор на его внутренний массив. После перераспределения ранее существовавшие итераторы элементов могут стать недействительными, но адаптер продолжает обращаться к контейнеру и выполнять push_back.
Вопрос: почему back_inserter нельзя использовать для контейнера без push_back, даже если в него можно вставлять элементы другим способом?
Потому что данный адаптер жёстко основан на операции push_back. Например, для std::set он неприменим: у множества нет push_back, а вставка должна учитывать сортировку и уникальность ключей. Для такого контейнера используют подходящий алгоритм вставки или std::inserter с позицией, если соответствующая операция поддерживается.