#c #stl #iterator
Вопрос:
У меня есть a std::list
, в который я вставляю элементы, и у меня есть std::unordered_map
место, где я хочу хранить итераторы для элементов, вставленных в std::list
(я внедряю кэш LRU). Следующий код не дает мне ожидаемого результата:
#include <list>
#include <unordered_map>
#include <iostream>
int main()
{
std::list<int> l;
std::unordered_map<int, std::list<int>::iterator> listItems;
for (int i = 0; i < 5; i )
{
l.push_back(i);
listItems[i] = std::end(l);
}
for (int i = 0; i < 5; i )
std::cout << *(listItems[i]) << " ";
std::cout << std::endl;
}
Результат здесь таков 5 5 5 5 5
— результат, которого я хочу/ожидаю 0 1 2 3 4
. Я бы предположил, посмотрев на этот код, который std::end
возвращает итератор к последнему элементу списка, который копируется в ListItems[i], но это явно не то, что происходит. Я в замешательстве, почему добавление элементов в список влияет на результат предыдущих вызовов std::end
Однако, если я изменю первый цикл на
for (int i = 0; i < 5; i )
{
l.push_front(i);
listItems[i] = std::begin(l);
}
Я получаю результат, которого ожидаю — 0 1 2 3 4
. Так в чем же здесь разница между push_front
и push_back
, и std::begin
и std::end
Комментарии:
1. std::end возвращает итератор к одному за концом контейнера. Предполагается, что вы никогда не будете разыменовывать его.
*(listItems[i])
это неопределенное поведение.2. В этом отношении либо
push_back
илиpush_front
может привести к аннулированию всех ранее назначенных итераторов, поэтому вся ваша структура данных проблематична.3. @MarkRansom не могли бы вы подробнее рассказать об этом? при каких условиях это могло произойти?
4. @AlexArmbruster Есть хорошая сводка о том, когда итераторы становятся недействительными по адресу cppreference.com . (Несмотря на этот предыдущий комментарий,
push_back()
ниpush_front()
один из итераторов a не является недействительнымstd::list
. Этоvector
,deque
,unordered_set
иunordered_multiset
чьи итераторы могут быть признаны недействительными при вставке.)5. @AlexArmbruster моя ошибка. Я не заметил, что ты употребляла
std::list
и неstd::vector
употребляла .
Ответ №1:
Чтобы получить итератор последнего элемента, это может быть достигнуто с помощью: std::prev(std::end(l))
. Ваш код сохранил конечный итератор и разыменовал его, это UB.
И док из std::list::end
:
Возвращает итератор к элементу, следующему за последним элементом контейнера, этот элемент действует как заполнитель; попытка доступа к нему приводит к неопределенному поведению.
Для std::begin
того , чтобы мы получили итератор к первому элементу контейнера, это безопасно, уважайте его и получите соответствующий элемент.
#include <iostream>
#include <list>
#include <unordered_map>
int main() {
std::list<int> l;
std::unordered_map<int, std::list<int>::iterator> listItems;
for (int i = 0; i < 5; i ) {
l.push_back(i);
listItems[i] = std::prev(std::end(l));
}
for (int i = 0; i < 5; i ) std::cout << *(listItems[i]) << " ";
std::cout << std::endl;
}
Комментарии:
1. @MarkRansom, с которым ты путаешь
std::list
std::vector
.2. Контейнеры на основе узлов, такие как
std::list
, не допускают недействительности указателя при добавлении или удалении других элементов. Я не вижу ничего плохого в этом коде.3. @ChrisUzdavinis
back()
дает вам элемент, но не итератор, чего хочет OP.4. @EtiennedeMartel ты слишком быстр. Я буквально исправил это через 12 секунд после написания, и вы уже прокомментировали. 🙂
5. @prehistoricpenguin извини, я тут подумал
std::vector
.std::list
нет той проблемы, на которую я указывал.