Передача параметров итератора алгоритма std по значению vs по ссылке

Мне интересно, почему во многих шаблонных алгоритмах в STL аргументы не передаются по ссылке, а скорее по значению. Вот пример из заголовка <iterator > :

template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance (InputIterator first, InputIterator last);

Когда я передаю две итератора этой функции, они копируются. Мои наивные мысли состоят в том, что было бы лучше передать эти итераторы с помощью const-ссылки, чтобы избежать копирования объектов итератора:

template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance (const InputIterator &first, const InputIterator &last);

Можно сказать, что итераторы - это вообще очень маленькие объекты и что их копирование не дорого. Но даже еще: дешевое копирование будет дороже, чем вообще никакого копирования.

Итак, в чем причина того, что в STL-версии итераторы передаются по значению?

Спасибо!

Ответ 1

Одна вещь, которая приходит мне на ум и которая против const ness в ссылке: итераторы должны быть изменены при их использовании.

Еще одна деталь реализации может заключаться в том, что итераторы действительно реализованы как указатели. Так что ссылки в большинстве случаев. Если вы передадите указатель по значению, вы скопируете его один раз, но разыщите его только по необходимости. Если, однако, сам итератор-указатель передается указателем-указателем, тогда , который должен быть разыменован первым, просто чтобы добраться до итератора, и это нужно делать каждый раз, когда итератор доступен. Это лишнее.

Ответ 2

Для некоторых типов с дешевой копией передача их по значению происходит быстрее, чем передача по ссылке. Обычная мудрость из учебных пособий и т.д. Утверждает, что по ссылке быстрее, но это не обязательно относится к типам дешевой копии.

Как вы передаете объект по значению? Вы делаете его копию, что означает, что вы берете значение и вставляете его в стек для вызова функции. И как вы проходите по ссылке? Вы нажимаете адрес памяти в стек, а затем вызываемая функция должна извлекать все, что находится по этому адресу. Теперь оптимизация и кэширование могут вступить в игру, чтобы сделать выборку памяти намного дешевле, но вы все равно не получите дешевле, чем точное значение из стека. Что в случае итераторов часто бывает простым, как простой указатель. Это одно слово длинное и очень дешевое для копирования.

Также обратите внимание, что вы предлагаете передать ссылку на const. Это означает, что все равно придется копировать вызываемую функцию, чтобы она могла быть изменена (например, приращение в цикле).

Ответ 3

Понятие тега std - это обобщение указателя. Итераторы контейнеров std обычно реализуются как тип, который состоит из одного указателя. В случае типа аргумента, который так же дешево копировать как указатель, передача аргумента по ссылке дороже, чем передача его по значению. Ссылка на объект должна быть разыменована до того, как значение объекта может быть использовано. Подробнее см. этот ответ.

Поскольку почти все алгоритмы std должны делать копии итераторов, для получения наилучшей производительности уже необходимо, чтобы итератор был дешев для копирования. По этой причине очень необычно найти итератор, который значительно дороже, чем по ссылке.

В случае std::distance - и многих других алгоритмов - весь алгоритм настолько прост, что вызов, скорее всего, будет встроен компилятором. Если вызов встроен, не имеет значения, передан ли аргумент по ссылке или по значению. [Обратите внимание, что вызов встроенной функции не совпадает с объявлением встроенной функции!]

В случае, когда итератор дороже передавать по значению, чем по ссылке, а вызов функции не является встроенным, вы можете сделать аргумент для передачи итераторов по rvalue-reference. Увеличение производительности в таком редком случае, вероятно, не стоит дополнительной сложности.

Ответ 4

Большинство алгоритмов изменяют свои аргументы. Например, distance может быть реализовано следующим образом 1:

template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type
distance (InputIterator first, InputIterator last) {
    typename iterator_traits<InputIterator>::difference_type result{};
    while (first++ != last)
        ++result;
    return result;
}

Очевидно, что это не работает, если вы передаете first в качестве ссылки const.

И он также не работает, если вы передаете его как ссылку const, потому что в большинстве контекстов вызова объекты-вызывающие не могут быть изменены (например, если вы передаете результат container.begin() в функцию.

Конечно, вы все равно можете пройти по ссылке const, сделать копию внутри функции и затем изменить ее. В какой момент wed ничего не получил.

В сочетании со стандартной рекомендацией С++, чтобы итераторы были легкими типами, которые должны быть дешевыми для копирования, более простая и в большинстве случаев более эффективная передача их по значению:

Но даже еще: дешевое копирование было бы дороже, чем вообще не копирование.

Не верно. Вам также нужно скопировать ссылку (которая, в случае вызова не-встроенной функции, будет реализована как указатель). И тогда вам нужно разыменовать этот указатель, который также добавляет лишние накладные расходы. По сравнению с прямой копией, которая может быть столь же дешевой, как копирование указателя во многих случаях, и не несет никаких накладных расходов на разглашение.


1 Конечно, реальная реализация была бы оптимизирована для итераторов с произвольным доступом, чтобы иметь постоянное, а не линейное время выполнения. Вышеупомянутая реализация предназначена только для изложения.