Страницы

Поиск по вопросам

четверг, 19 декабря 2019 г.

Удаление элемента из контейнера используя синтаксис цикла for c++11

#cpp #cpp11 #stl


Возможно ли, используя синтаксис диапазонного цикла for:

for(auto& i: mapR) {
    // ...
} 


для обхода контейнера, удалить текущий элемент из контейнера, если он, например,
попадает под некое условие? Или придется использовать версию с итераторами, как показано
ниже?

for(auto i = mapR.begin(); i != mapR.end(); ) {   
    // ... 
    i = mapR.erase(i); 
}

    


Ответы

Ответ 1



Стандарт определяет range-based for loop, эквивалентным следующему коду: { auto && __range = range - init; for(auto __begin = begin - expr, __end = end - expr; __begin != __end; ++__begin) { for-range-declaration = *__begin; statement } } Как вы можете видеть, внутри используются итераторы, поэтому, внутри цикла, нельзя использовать код, который может сделать итераторы недействительным. Отвечая на Ваш вопрос, нет — нельзя удалять элементы из C++ контейнеров, в которых данная операция приводит к недействительным итераторам.

Ответ 2



Диапазонная версия цикла for вида for ( for-range-declaration : expression ) statement эквивалентна следующему коду: { auto && __range = range-init; for ( auto __begin = begin-expr, __end = end-expr; __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } } Поэтому формально с элементами контейнера внутри диапазонного цикла for можно было бы делать всё то же самое, что и с элементами внутри эквивалентного ему обычного цикла for. Т.е. учитывая все последствия инвалидации ссылок/итераторов/указателей соответствующего контейнера. Но проблема в том, что диапазонная версия for не предоставляет программисту доступа непосредственно к итератору и работать можно только со значениями или ссылками. Всё это приводит к тому, что искать элемент надо по-новой, а если контейнер содержит несколько эквивалентных элементов, то нельзя достоверно сказать, что удаляется именно тот элемент, на котором в данный момент находится итератор цикла. В итоге получаем, что диапазонная версия for не предусмотрена для модификации контейнера в плане вставки/удаления элементов. Такой цикл следует использовать только для перебора значений или их модификации.

Ответ 3



В любом случае ваш вариант удаления некорректен, т.к. при удалении формально происходит инвалидация итераторов. Вот ваш вариант, который вы считаете корректным: vector tst{ 1,1,1,2,2,2,3,3,3,4,4,4,5,5,5}; for(auto i = tst.begin(); i != tst.end(); ++i) { if (*i <= 3) i = tst.erase(i); } for(auto i: tst) cout << i << endl; Получаем (Visual C++ 2015) - 1 2 2 3 4 4 4 5 5 5 C GCC - та же история: http://ideone.com/MctlQw Update Предложенный вариант if (*i <= 3) i = tst.erase(i); else ++i; лично у меня вызывает неприятие по причине стиля (вынесение инкремента из заголовка цикла, на мой взгляд, никак не способствует восприятию текста). переносимости. По крайней мере тут четко говорится, что erase invalidates iterators and references at or after the point of the erase, including the end() iterator. С другой стороны, с интересом выслушаю и другие мнения, поскольку опять же формально, при перевычислении end() данный способ в принципе должен срабатывать.

Комментариев нет:

Отправить комментарий