Добавление элементов в вектор во время цикла на основе диапазона 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), выдаст исключение.

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