Разрешено ли добавлять элементы в предварительно выделенный вектор в цикле 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 (если эта документация неверна и стандарт не говорит иначе)