Добавление элементов в вектор во время цикла на основе диапазона C++11
Я использовал новый цикл for, основанный на диапазоне, предоставленный стандартом C++11, и у меня возник следующий вопрос: предположим, что мы перебираем vector<>
используя диапазон на основе for
и мы добавим некоторый элемент в конец вектора во время этой итерации. Итак, когда заканчивается цикл?
Например, посмотрите этот код:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<unsigned> test({1,2,3});
for(auto &num : test) {
cout << num << " ";
if(num % 2)
test.push_back(num + 10);
}
cout << "\n";
for(auto &num : test)
cout << num << " ";
return 0;
}
Я тестировал G++ 4.8 и Apple LLVM версии 4.2 (clang++) с флагом "-std= C++11", и вывод (для обоих):
1 2 3
1 2 3 11 13
Обратите внимание, что первый цикл заканчивается в конце исходного вектора, хотя мы добавляем к нему другие элементы. Кажется, что цикл for-range оценивает конец контейнера только в начале. Действительно ли это правильное поведение диапазона? Это указано комитетом? Можем ли мы доверять такому поведению?
Обратите внимание, что если мы изменим первый цикл
for(vector<unsigned>::iterator it = test.begin(); it != test.end(); ++it)
с недействительными итераторами и придумать ошибку сегментации.
2 ответа
Нет, вы не можете полагаться на это поведение. Изменение вектора внутри цикла приводит к неопределенному поведению, поскольку итераторы, используемые циклом, становятся недействительными при изменении вектора.
Диапазон на основе цикла
for ( range_declaration : range_expression) loop_statement
по существу эквивалентно
{
auto && __range = range_expression ;
for (auto __begin = std::begin(__range),
__end = std::end(__range);
__begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
Когда вы изменяете вектор, итераторы __begin
а также __end
больше не действительны и разыменование __begin
приводит к неопределенному поведению.
Вы можете полагаться на это поведение, пока емкость вектора достаточно велика, поэтому перераспределение не требуется. Это гарантировало бы достоверность всех итераторов и ссылок, кроме итераторов прошлого цикла, после вызова push_back()
, Это хорошо, так как end()
вычислялся только один раз в начале цикла (см. std:: vector:: push_back).
В вашем случае вы должны увеличить емкость test
вектор, чтобы гарантировать это поведение:
vector<unsigned> test({1,2,3});
test.reserve(5);
РЕДАКТИРОВАТЬ
На основе ответов на вопрос: законно ли добавлять элементы в предварительно выделенный вектор в цикле for для этого вектора?, это случай неопределенного поведения. Версия выпуска, созданная с помощью gcc, clang и cl, работает нормально, но отладочная версия, созданная с помощью cl (Visual Studio), выдаст исключение.