Подтвердить что ты не робот

Является ли законным добавлять элементы к предварительно распределенному вектору в цикле, зависящем от диапазона, над этим вектором?

Я использую Visual Studio 2015 Update 1 С++-компилятор и этот фрагмент кода:

#include <iostream>
#include <vector>

using namespace std;

int main()
{
  vector<int> v{3, 1, 4};

  v.reserve(6);

  for (auto e: v)
    v.push_back(e*e);

  for (auto e: v)
    cout << e << " ";

  return 0;
}

Версия выпуска работает нормально, но отладочная версия создает сообщение об ошибке vector iterators incompatible. Почему это?

Прежде чем вы укажете это как дублирующийся вопрос на Добавить элементы в вектор во время цикла С++ 11, прочитайте мой ответ qaru.site/info/240479/... с аргументами обратного.

4b9b3361

Ответ 1

В вашем коде отображается поведение undefined, но это сложно, и его можно просто поймать в отладочных сборках.

Когда вы выполняете v.push_back, все итераторы недействительны, если размер пропускает емкость. Вы избегаете этого с резервом.

Однако, даже если вы не создаете возможности для роста, итератор, прошедший через конец, все еще недействителен. В общем, правила аннулирования итератора не различают "значение итератора" будет мусором/ссылаться на другой объект ", а" местоположение итератора "больше недействительно". Когда это происходит, итератор просто считается недействительным. Поскольку конечный итератор больше не является конечным итератором (он идет от ссылки на ничего, ссылаясь на что-то почти в каждой реализации), стандарт просто утверждает, что он недействителен.

Этот код:

for (auto e: v)
  v.push_back(e*e);

расширяется примерно до:

{
  auto && __range = v; 
  for (auto __begin = v.begin(),
    __end = v.end(); 
    __begin != __end;
    ++__begin
  )
  { 
       auto e = *__begin;
       v.push_back(e*e);
  }
}

вызов v.push_back делает недействительным итератор __end, который затем сравнивается с ним, а сборка отладки корректно указывает на поведение undefined как проблему. Отладочные итераторы MSVC довольно осторожны с правилами недействительности.

Конструкция релиза выполняет поведение undefined, а так как векторный итератор в основном представляет собой тонкую оболочку вокруг указателя, а указатель на элемент, прошедший мимо конца, становится указателем на последний элемент после отталкивания без переполнение емкости, оно "работает".

Ответ 2

Согласно документации:

Если новый размер() больше емкости(), то все итераторы и ссылки (включая итератор прошедшего конца) недействительны. В противном случае недействителен только последний итератор конца.

В нем говорится, что даже если емкость достаточна, то итератор завершен, поэтому я считаю, что ваш код имеет UB (если эта документация не указана, а в стандарте указано иное)