почему значения, возвращаемые std::end (), изменяются при изменении контейнера, но не с помощью std::begin()?

#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 нет той проблемы, на которую я указывал.