Каковы преимущества std::distance по сравнению с вычитанием итераторов?
Я итерируюсь по вектору и мне нужен индекс, на который указывает итератор. Какие достоинства и недостатки у следующих методов?
it - vec.begin()
std::distance(vec.begin(), it)
5 ответ(ов)
Я бы предпочел использовать it - vec.begin()
, именно по противоположной причине, указанной Навином: так код не скомпилируется, если вы замените вектор на список. Если делать это на каждой итерации, вы можете легко превратить алгоритм с временной сложностью O(n) в алгоритм с временной сложностью O(n^2).
Другой вариант, если вы не переходите по контейнеру во время итерации, — сохранить индекс как второй счетчик цикла.
Примечание: it
— это распространенное название для итератора контейнера, std::container_type::iterator it;
.
Я бы предпочел использовать std::distance(vec.begin(), it)
, так как это позволит мне сменить контейнер без необходимости вносить изменения в код. Например, если вы решите использовать std::list
вместо std::vector
, который не предоставляет итератор случайного доступа, ваш код все равно будет компилироваться. Поскольку std::distance
автоматически выбирает оптимальный метод в зависимости от характеристик итераторов, у вас также не будет ухудшения производительности.
Как уже показали UncleBens и Naveen, у каждого из этих подходов есть свои весомые причины. Какой из них "лучше", зависит от того, какое поведение вы хотите получить: хотите ли вы гарантировать постоянное время выполнения, или допускаете переход на линейное время, когда это необходимо?
Выражение it - vec.begin()
выполняется за постоянное время, но оператор -
определён только для итераторов произвольного доступа, поэтому код вообще не скомпилируется, если вы используете итераторы списков, например.
С другой стороны, std::distance(vec.begin(), it)
работает с любыми типами итераторов, но будет выполняться за постоянное время только в случае итераторов произвольного доступа.
Таким образом, ни один из этих методов не является однозначно "лучше". Используйте тот, который соответствует вашим требованиям.
Мне нравится этот вариант: it - vec.begin()
, потому что, на мой взгляд, он четко показывает "расстояние от начала". С итераторами мы привыкли мыслить в терминах арифметики, поэтому знак -
является самым очевидным индикатором в данном контексте.
Если вы уже ограничили свой алгоритм использованием только std::vector::iterator
, то в конечном итоге не имеет значения, какой метод вы выберете. Ваш алгоритм уже стал слишком конкретным, и выбор одного метода вместо другого не приведет к значимым изменениям. Оба они выполняют одну и ту же операцию, и тут дело только в ваших предпочтениях. Лично я бы использовал явное вычитание.
Если же вы хотите сохранить более высокий уровень обобщенности в вашем алгоритме, позволяя возможность применения к другим типам итераторов в будущем, то лучший выбор метода будет зависеть от ваших намерений. Это зависит от того, насколько ограничительными вы хотите быть в отношении типов итераторов, которые можно использовать.
- Если вы используете явное вычитание, ваш алгоритм будет ограничен довольно узким классом итераторов: итераторами произвольного доступа. (Это то, что вы получаете от
std::vector
.) - Если вы используете
std::distance
, ваш алгоритм будет поддерживать гораздо более широкий класс итераторов: итераторы ввода.
Конечно, вычисление std::distance
для не произвольно-доступных итераторов в общем случае является неэффективной операцией (в то время как для произвольно-доступных это так же эффективно, как вычитание). Вам решать, имеет ли ваш алгоритм смысл для не произвольно-доступных итераторов с точки зрения эффективности. Если потеря эффективности критична до такой степени, что делает ваш алгоритм совершенно бесполезным, то лучше придерживаться вычитания, тем самым предотвращая неэффективные применения и заставляя пользователя искать альтернативные решения для других типов итераторов. Если же эффективность с не произвольно-доступными итераторами все еще находится в приемлемых пределах, то лучше использовать std::distance
и задокументировать тот факт, что алгоритм работает лучше с итераторами произвольного доступа.
Как узнать, присутствует ли элемент в std::vector?
Как проще всего инициализировать std::vector с жестко заданными элементами?
Как удалить элемент из std::vector<> по индексу?
Как проверить, существует ли заданный ключ в std::map?
Почему `std::initializer_list` не поддерживает оператор подиндексации?