Разрешено ли добавлять элементы в предварительно выделенный вектор в цикле for на основе этого диапазона?

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

#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 сообщение об ошибке. Это почему?

Прежде чем пометить его как дублирующий вопрос для добавления элементов в вектор во время цикла на основе диапазона C++ 11, прочитайте мой ответ /questions/18986524/dobavlenie-elementov-v-vektor-vo-vremya-tsikla-na-osnove-diapazona-c11/18986526#18986526 с аргументами об обратном.

2 ответа

Решение

Ваш код демонстрирует неопределенное поведение, но он сложен и имеет тенденцию быть пойманным только в отладочных сборках.

Когда вы делаете 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 итератор, который затем сравнивается, и отладочная сборка правильно отмечает неопределенное поведение как проблему. Отладчик MSVC итераторы очень осторожны с правилами аннулирования.

Сборка релиза имеет неопределенное поведение, и поскольку векторный итератор является в основном тонкой оболочкой вокруг указателя, а указатель на элемент past-the-end становится указателем на последний элемент после возврата назад без переполнения емкости, it " работает".

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

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

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

Другие вопросы по тегам