Как std:: copy работает с итераторами потоков

Обычная конструкция STL:

vector<string> col;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
    back_inserter(col));

где мы используем istream_iterator для копирования из std-входа (cin) в вектор.

Может ли кто-нибудь объяснить, как работает этот код?

моя проблема в том, что я действительно не понимаю эту часть:

istream_iterator<string>(cin), istream_iterator<string>()

Ответ 1

Во-первых, обратите внимание, что в этом случае нет реальной необходимости использовать std::copy вообще. Вы можете просто инициализировать вектор непосредственно из итераторов:

vector<string> col((istream_iterator<string>(cin)),
                    istream_iterator<string>());

Это, вероятно, не делает код намного понятнее.

Насколько работает код, он, вероятно, немного более страшен, чем вы думаете. Istream_iterator выглядит смутно следующим образом:

template <class T>
class istream_iterator { 
    std::istream *is;
    T data;
public:
    istream_iterator(std::istream &is) : is(&is) { ++(*this); }
    istream_iterator() : is(nullptr) {}

    T operator++() { (*is) >> data; return *this; }
    T operator++(int) { (*is) >> data; return *this; }

    T const &operator*() { return data; }   

    bool operator !=(istream_iterator &end) { return (*is).good(); }
    bool operator ==(istream_iterator &end) { return !(*is).good(); }
};

Очевидно, что более того я пропущу, но это больше всего нас беспокоит. Итак, что происходит, когда вы создаете итератор, он читает (или пытается) элемент из потока в переменную, которую я назвал data. Когда вы разыскиваете итератор, он возвращает data. Когда вы увеличиваете итератор, он читает (или пытается) следующий элемент из файла. Несмотря на то, что они написаны так, как будто они сравнивают один итератор с другим, operator== и operator!= действительно просто проверяют конец файла 1.

То, что затем используется std::copy, которое (снова упрощенное) выглядит смутно следующим образом:

template <class InIt, class OutIt>
void std::copy(InIt b, InIt e, OutIt d) { 
    while (b != e) {
        *d = *b;
        ++b;
        ++d;
    }
}

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


  • Обратите внимание, что технически это не соответствует поведению. Я упростил сравнение, чтобы все было просто. Два итератора, построенные по умолчанию, должны сравнивать одинаковые значения, и если вы построите два итератора из одного потока, они должны сравниться как минимум, прежде чем вы что-нибудь прочитаете из потока. Это мало практических различий - единственное сравнение, которое вы видели в реальном использовании, - это определить, достигли ли вы конца файла.

Ответ 2

Часть ответа ниже цитируется в стандартной библиотеке С++: учебник и ссылка Николая М. Йосуттиса с небольшими ухищрениями.

Выражение

  istream_iterator<string>(cin)  

создает итератор строки, который читает из стандартного потока ввода cin. Аргумент template string указывает, что итератор потока считывает элементы этого типа. Эти элементы считываются с помощью обычного оператора ввода → . Таким образом, каждый раз, когда алгоритм хочет обработать следующий элемент, итератор istream преобразует это желание в вызов

cin >> string  

Оператор ввода для строк обычно читает одно слово, разделенное пробелами.

Выражение

istream_iterator<string>()  

вызывает конструктор по умолчанию итераторов istream, который создает так называемый итератор конца потока. Он представляет поток, из которого вы больше не можете читать. Итератор конца строки используется как end of the range, поэтому алгоритм copy считывает все строки из cin, пока он больше не сможет читать больше.

Последний:

back_inserter(col))

согласно документации back_inserter:

A std:: back_insert_iterator, который можно использовать для добавления элементов в конец контейнера c

Он добавит все строки в строки col.

Вы можете найти информацию о std:: istream_iterator и std:: back_inserter для деталей.