Я рекомендую против std::forward_list
точно так же, как я рекомендую против std::list
почти во всех ситуациях. Лично я никогда не встречал в своем коде ситуацию, в которой связанный список был лучшей структурой данных.
В C ++ ваш сбор данных по умолчанию должен быть std::vector
. Это дает вам эффективный push_back
, если это то, что вам действительно нужно. Технически это не дает вам эффективного удаления и вставки с середины, если вы только посмотрите на абстрактные измерения сложности big-O этой одной операции. В реальном мире, однако, std::vector
все еще выигрывает даже для вставки и удаления в середине .
В качестве примера Бьярн Страуструп создал тест на 100 000 элементов std::list
против std::vector
. Он будет искать каждый элемент и удаляет его. Затем он найдет точку вставки и вставит в середину. Он мог бы использовать двоичный поиск на std::vector
, но не сделал сравнение «более справедливым».
Результаты показывают сильную победу для std::vector
, даже в этой ситуации, когда std::list
должен быть сильным. Простой обход std::list
занимает намного больше времени из-за того, как далеко в памяти находятся все объекты. std::list
не поддерживает кеш, что, пожалуй, самое важное для современных процессоров.
Полный доклад Бьярна Страуструпа
Точное объяснение эффектов, с эталонными показателями для нескольких размеров
Обратите внимание, что эта вторая ссылка здесь дает некоторые ситуации, когда вы, возможно, захотите использовать std::list
, например, когда размер элементов велик. Однако я был в ситуации, когда у меня было много элементов в определенном порядке, и мне нужно было их удалить.
Эти элементы были больше, чем любой встроенный тип, но не очень большие (возможно, по 20-30 байт каждый на 32-битном компьютере). Количество элементов было достаточно большим, так что вся моя структура данных составляла несколько сотен МБ. Сбор данных представлял собой набор значений, которые теоретически могут быть действительными на основе известной в настоящее время информации. Алгоритм итерировал по всем элементам и удалял элементы, которые больше не могли быть действительными, основываясь на новой информации, причем каждый проход, вероятно, удалял где-то около 80% оставшихся элементов.
Моей первой реализацией был простой std::vector
подход, при котором я удалял недопустимые элементы при прохождении. Это работало для небольших наборов тестовых данных, но когда я пытался сделать что-то реальное, это было слишком медленно, чтобы быть полезным. Я переключился на std::list
в качестве контейнера, но использовал тот же алгоритм, и я увидел значительные улучшения производительности. Однако это было все еще слишком медленно, чтобы быть полезным. Победным изменением было переключиться обратно на std::vector
, но вместо того, чтобы удалять элементы на месте, которые были плохими, я создал новый std::vector
, и любые элементы, которые я нашел, которые были хорошими, были вставлены в этот std::vector
, а затем в конце функции я просто отбрасывал старый std::vector
и использовал новый, и это давало мне примерно такую же скорость, чем std::list
, как std::list
давал мне по сравнению с моим исходным std::vector
реализации, и это было достаточно быстро, чтобы быть полезным.