Уменьшение итератора после каждой итерации цикла показывает странное поведение

Я создал программу, чтобы попытаться попрактиковаться в семантике структуры данных списка. Я заметил странную разницу в следующих фрагментах кода:

Первый код:

#include<iostream>
#include<list>

using namespace std;

int main() {
    list<int> l;
    int n = 100;
    for(int i = 0; i < n; i++) {
        l.push_back(i);
    }
    list<int>::iterator it = l.end();
    it--;
    for(; !l.empty(); it--) {
        cout << "the size of l is " << (int) l.size() << endl;
        l.erase(it);
    }
}

Второй код:

#include<iostream>
#include<list>

using namespace std;

int main() {
    list<int> l;
    int n = 100;
    for(int i = 0; i < n; i++) {
        l.push_back(i);
    }
    list<int>::iterator it = l.end();
    it--;
    for(; !l.empty();) {
        cout << "the size of l is " << (int) l.size() << endl;
        l.erase(it--);
    }
}

Цель обоих фрагментов кода проста — просто стереть все элементы в списке.

Единственная разница между ними — место, где итератор списка уменьшается. В первом примере кода я использовал поток управления цикла for для уменьшения значения итератора. Во втором я использовал оператор постдекремента для декремента итератора.

Насколько я понимаю, приведенные выше примеры кода должны быть эквивалентны, потому что я уменьшаю итератор немедленно после удаления элемента из списка. Кроме того, согласно документам STL, только итератор стертого элемента в списке становится недействительным. Поэтому не должно быть никакого неопределенного поведения.

Проблема в том, что второй пример кода работает как положено - он останавливается после стирания всех элементов в списке. Однако для первого примера размер списка мог стать даже отрицательным?! Когда я попытался увеличить начальное количество элементов в списке, первая программа вылетает на полпути.

Может ли кто-нибудь посоветовать мне, почему эти образцы кода ведут себя по-разному?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
123
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

1-й код имеет неопределенное поведение. Как вы сказали, erase делает итератор недействительным, it-- оценивается после этого, что приводит к UB.

Второй код в порядке; обратите внимание, что порядок оценки отличается. it-- уменьшит итератор, а затем вернет исходное значение (это точка оператора пост-декремента). Исходное значение передается erase позже. Декремент происходит до erase, так что все в порядке.

+1 Спасибо за разъяснение. Я интерпретировал это так: сначала происходит стирание, а затем уменьшение. Следовательно, я запутался.

Donald 19.06.2019 08:09

Другими словами, поскольку оператор декремента возвращает значение исходного итератора, может произойти erase, и итератор все еще может быть действительным. В отличие от другого кода, который уменьшает уже недопустимый итератор. Просто пытаюсь сказать что-то по-другому, чтобы, возможно, большему количеству людей было легче понять.

user10957435 19.06.2019 08:10

@LanceHAOH Вы должны пропустить приятный момент оператора пост-декремента. :)

songyuanyao 19.06.2019 08:12

@songyuanyao Мне сейчас плохо. Ты прав. Из документов STL «Постинкремент и постдекремент создают копию объекта, увеличивают или уменьшают значение объекта и возвращают копию до увеличения или уменьшения». Всю свою жизнь я думал, что оператор постдекремента/инкремента вернет исходное значение, за которым следует применение операции инкремента/декремента.

Donald 19.06.2019 08:24

@ChristinaJacob Undefined Behavior = Все может случиться.

Donald 19.06.2019 08:26

@LanceHAOH Посмотрите на это под другим углом, как на оператор (или функцию), когда он возвращается, вызов завершается. Невозможно выполнить декремент после возврата. Поэтому он должен выполнить декремент, а затем вернуть исходное скопированное значение.

songyuanyao 19.06.2019 08:27

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