Я создал программу, чтобы попытаться попрактиковаться в семантике структуры данных списка. Я заметил странную разницу в следующих фрагментах кода:
Первый код:
#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, только итератор стертого элемента в списке становится недействительным. Поэтому не должно быть никакого неопределенного поведения.
Проблема в том, что второй пример кода работает как положено - он останавливается после стирания всех элементов в списке. Однако для первого примера размер списка мог стать даже отрицательным?! Когда я попытался увеличить начальное количество элементов в списке, первая программа вылетает на полпути.
Может ли кто-нибудь посоветовать мне, почему эти образцы кода ведут себя по-разному?





1-й код имеет неопределенное поведение. Как вы сказали, erase делает итератор недействительным, it-- оценивается после этого, что приводит к UB.
Второй код в порядке; обратите внимание, что порядок оценки отличается. it-- уменьшит итератор, а затем вернет исходное значение (это точка оператора пост-декремента). Исходное значение передается erase позже. Декремент происходит до erase, так что все в порядке.
Другими словами, поскольку оператор декремента возвращает значение исходного итератора, может произойти erase, и итератор все еще может быть действительным. В отличие от другого кода, который уменьшает уже недопустимый итератор. Просто пытаюсь сказать что-то по-другому, чтобы, возможно, большему количеству людей было легче понять.
@LanceHAOH Вы должны пропустить приятный момент оператора пост-декремента. :)
@songyuanyao Мне сейчас плохо. Ты прав. Из документов STL «Постинкремент и постдекремент создают копию объекта, увеличивают или уменьшают значение объекта и возвращают копию до увеличения или уменьшения». Всю свою жизнь я думал, что оператор постдекремента/инкремента вернет исходное значение, за которым следует применение операции инкремента/декремента.
@ChristinaJacob Undefined Behavior = Все может случиться.
@LanceHAOH Посмотрите на это под другим углом, как на оператор (или функцию), когда он возвращается, вызов завершается. Невозможно выполнить декремент после возврата. Поэтому он должен выполнить декремент, а затем вернуть исходное скопированное значение.
+1 Спасибо за разъяснение. Я интерпретировал это так: сначала происходит стирание, а затем уменьшение. Следовательно, я запутался.