Каковы основные отличия между Итератором ржавчины и Итератором С++?

Типичный пример итератора С++ является указателем и может использоваться для указания элемента в массиве C следующим образом:

int array[] = {1, 2, 3, 4};
int* begin = std::begin(array); //Starting iterator
int* end = std::end(array) //Ending iterator

for(int* i = begin; i < end; i++)
{
    std::cout << *i << ',';
}

//Prints 1, 2, 3, 4

Это достаточно просто. Определение итератора из cplusplus.com -

Итератором является любой объект, который, указывая на некоторый элемент из диапазона элементов (например, массив или контейнер), имеет возможность итерации по элементам этого диапазона с помощью набора операторов...

Это имеет смысл; в приведенном выше коде были два итератора (итераторы begin и end), и он использовал цикл for и увеличивал.

В Rust используется итератор следующим образом:

let vect = vec![1, 2, 3, 4];

let vect_iter = vect.iter();

Что? Чтобы повторить его, выполните следующие действия:

vect_iter.next();
vect_iter.next();

Я не мог найти точное определение указателя в документах Rust, но, смотря на Iterator trait, кажется что итератор является оболочкой для контейнера, что позволяет упростить обработку, стандартизируя логику в некотором смысле (если это имеет смысл вообще).

Основные вопросы, которые у меня есть:

  • В чем основные отличия?
  • Почему у Rust есть итераторы таким образом и почему они выражаются так по-другому?
  • Существуют ли итераторы типа Rust в С++?
  • Существуют ли итераторы типа С++ в Rust?
  • Они называются что-то конкретное? (Внутренний/внешний?)

Ответ 1

Итератор - это концепция, найденная в языках программирования, для ссылки на конструкцию, которая позволяет выполнять итерацию над наборами или последовательностями элементов. Концепция преднамеренно расплывчата, это концепция! Он не предписывает какой-либо конкретной реализации.

Чтобы легче отличить С++ от Rust, я буду использовать разные имена:

  • Итераторы С++ будут называться курсоры,
  • Итераторы ржавчины будут называться потоками.

Да, они абсолютно произвольны. Обратите внимание: если вы посмотрите на такие языки, как Java или С#, вы обнаружите, что они также используют потоки.


С++

Прежде всего, не используйте cplusplus.com. cppreference.com намного лучше.

Итератором является любой объект, который, указывая на некоторый элемент из диапазона элементов (например, массив или контейнер), имеет возможность итерации по элементам этого диапазона с помощью набора операторов...

Простой и неправильный.

Курсор может либо:

  • указывает на элемент,
  • или сингулярный и не указывать на какой-либо элемент вообще.

В общем случае сингулярное значение используется для представления:

  • конец последовательности для итерации: vec.end(),
  • отсутствие элемента: std::find(...).

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

Почему С++ использовал такое представление? Потому что, как C сделал это, и он работает очень хорошо... хотя он подвержен ошибкам.


Ржавчина

Rust стремится быть безопасным и благоприятствует API, которые просты в использовании. Это исключает пару курсоров:

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

Чтобы управлять границами, сглаживать и избегать парного несоответствия, вы должны использовать один объект; таким образом, подобный потоку API.

API-интерфейс Iterator в Rust похож на API Java и С#, хотя Rust улучшает его с помощью Option<T>, поэтому вместо неуклюжей пары hasNext()/next() он предлагает один метод next(), который продвигает поток и может сигнализировать о его окончании.


Заключение

Оба Rust и С++ имеют способ перебора элементов коллекции:

  • С++ предлагает C-образный способ, гибкий, но подверженный ошибкам,
  • Rust предлагает современный способ, безопасный, но менее гибкий.

Оба языка также предлагают внешнюю и внутреннюю итерацию:

  • Внешний: пользователь управляет итерацией (вызовы ++ или next()),
  • Внутренний: итератор управляет кодом пользователя (см. std::foreach и Iterator::foreach).

Ответ 2

Итераторы в Rust и С++ концептуально совершенно разные.

С++

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

Указатель в С++ является допустимым итератором, но есть и другие типы, которые являются итераторами.

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

#include <algorithm>
#include <iterator>
#include <cstdio>
#include <utility>

template <typename T, std::size_t N>
void reverse_array(T (&arr)[N]) {
    using std::swap;
    auto left = std::begin(arr), right = std::end(arr);
    while (left < right) {
        --right;
        swap(*left, *right);
        ++left;
    }
}

int main() {
    int x[] = {1, 2, 3, 4, 5};
    reverse_array(x);
    for (const auto it : x) {
        std::printf("%d\n", it);
    }
    return 0;
}

Но вы можете быстро обобщить его для работы с любым контейнером с двунаправленными итераторами:

#include <algorithm>
#include <iterator>
#include <list>
#include <cstdio>
#include <utility>

template <typename Iterator>
void reverse_any(Iterator left, Iterator right) {
    using std::swap;
    while (left != right) {
        --right;
        if (left == right)
            break;
        swap(*left, *right);
        ++left;
    }
}

int main() {
    std::list<int> list{1, 2, 3, 4, 5};
    reverse_any(std::begin(list), std::end(list));
    for (const auto it : list) {
        std::printf("%d\n", it);
    }
    return 0;
}

Ржавчина

В Rust итератор похож на кусочек. Итераторы ссылаются на последовательность объектов, и элементы могут быть доступны из итератора с помощью метода next(). В некотором смысле это означает, что итератор в Rust имеет итератор begin и end внутри него . Повторяя код С++ выше в Rust, вы получите что-то вроде этого:

fn reverse_any<'a, T: 'a, Iter>(mut iter: Iter)
where
    Iter: DoubleEndedIterator<Item = &'a mut T>,
{
    while let Some(left) = iter.next() {
        if let Some(right) = iter.next_back() {
            std::mem::swap(left, right);
        }
    }
}

fn main() {
    let mut v = [1, 2, 3, 4, 5];
    reverse_any(v.iter_mut());
    println!("{:?}", v);
}

Это имеет дополнительное преимущество безопасности. Недействительность Iterator является одним из наиболее распространенных источников ошибок в программах на С++, но Rust полностью устраняет проблему.

Стоимость заключается в том, что если вы хотите мутировать элементы, вы ограничены одним (возможно, двухсторонним) итератором в Rust, а на С++ вы можете иметь столько итераторов, сколько хотите работать с одним и тем же контейнером. Хотя однотипные и двухсторонние диапазоны являются наиболее распространенным случаем для итераторов, существуют некоторые алгоритмы, которые используют дополнительную гибкость, предоставляемую С++.

Один простой пример, о котором я могу думать, - это С++ std::remove_if. Прямая реализация remove_if будет использовать три итератора: два итератора для отслеживания диапазона проверяемых элементов и третий итератор для отслеживания записываемых элементов. Вы можете перевести std::remove_if в Rust, но он не сможет работать на обычных итераторах Rust и все еще модифицировать контейнер на месте.

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

Резюме

Итератор ржавчины почти эквивалентен парану итератора начала + конца С++. С++ позволяет использовать несколько итераторов и перемещать итераторы вперед и назад. Rust гарантирует, что вы случайно не используете недействительный итератор, но можете использовать только по одному, и он может двигаться только в одном направлении.

Я не знаю никакой терминологии для различения этих типов итераторов. Обратите внимание, что итераторы в стиле ржавчины гораздо более распространены, итераторы в С#, Python, Java и т.д. Работают одинаково, но могут иметь несколько разные имена (они называются "перечислениями" на С#).

Сноски

*: Технически это неверно. Вам нужно всего лишь иметь один итератор на С++, однако, как правило, функции пары и библиотеки обычно работают на парах итераторов (поэтому вам нужны "два итератора", если вы хотите использовать эти функции). Тот факт, что у вас есть пара (начало, конец), не означает, что последовательности ограничены, конечный итератор может быть бесконечно удален. Подумайте об этом как о наличии диапазона (0, ∞) в математике... ∞ на самом деле не число, это просто местозаполнитель, который позволяет вам знать, что диапазон неограничен справа.

: Помните, что только потому, что итератор "end" существует в С++, это не означает, что последовательность фактически имеет конец. Некоторые концевые итераторы в С++ похожи на бесконечность. Они не указывают на действительные элементы, и независимо от того, сколько раз вы переходите вперед, вы не достигнете бесконечности. В Rust эквивалентная конструкция является итератором, который никогда не возвращает None.

Ответ 3

Я вижу здесь три вещи. Пусть сломается.

Идея итератора

Когда вы вызываете С++ std::begin и Rust .iter() в своих примерах, вы получаете два "типа объектов", которые концептуально идентичны: итератор.

Если мы забудем о деталях реализации на мгновение, мы увидим, что цель и удобство использования итератора оказываются одинаковыми на обоих языках. Мы находим, что оба итератора:

  • "Объекты", которые могут быть созданы из коллекции ( "Итерируемый тип" )
  • Может быть расширен с использованием С++ std::advance и Rust .next()
  • Иметь "конец", определяемый С++ std::end и вывод Rust .next().

Это грубое упрощение, конечно, они похожи и разные по многим другим причинам, но это, вероятно, общий обзор, который вы ищете.

Реализация итератора

Несмотря на совместное использование общих тем, С++ и Rust очень разные языки и, естественно, будут реализовывать одну идею по-разному. Итераторы не являются исключением.

"Почему" слишком широк, чтобы на самом деле ответить здесь на "Переполнение стека". Ему нравится спрашивать, почему апельсины оранжевые и бананы не являются:)

Но вы, кажется, несколько смущены тем, как работать с Rust-реализацией итераторов, учитывая ваш комментарий:

Я не смог найти точное определение указателя в документах Rust

Не думайте, как программист на С++ прямо сейчас. Проверьте "Книга" , если вы еще не изучили понятия заимствования и владения; Это гораздо более типичный способ работы с данными, и требуется понять, как работают итераторы ржавчины.

Синтаксический сахар для итераторов

Оба С++ и Rust имеют "волшебство" в своих циклах for, которые позволяют легко работать с типами "итератор".

В отличие от вашего вопроса, это не концепция, уникальная для Rust. В С++ объект может использоваться с современным синтаксисом for (item : collection), если он реализует специальные методы, аналогично тому, как вы указали Iterator.

Резюме

В чем основные отличия?

Не много концептуально.

Почему у Rust есть итераторы таким образом и почему они выражаются так по-другому?

Это похоже на то, что так оно и есть. Они более похожи, чем вы думаете.

Существуют ли итераторы типа Rust в С++? Существуют ли итераторы типа С++ в Rust?

Они концептуально идентичны.

Они называются чем-то конкретным? (Внутренний/внешний?)

Может быть какая-то фантастическая академическая терминология для различий в реализации, но я не знаю об этом. Итератором является итератор.