2014-04-25 4 views
17

Я бы ожидал, что после, указывающего на символ n символов после того, как он начнется. Вместо этого он указывает на последний прочитанный символ. Почему это? то есть, если я делаю in_stream.tellg() до и после copy_n, они отличаются не на n, а на (n-1). Если бы я прочитал n персонажей с in_stream.read, то позиция была бы продвинута на n.Почему std :: copy_n не увеличивает итератор ввода n раз?

std::istreambuf_iterator<char> buf_iter(in_stream); 
std::copy_n(buf_iter, n, sym.begin()); 

Я посмотрел на реализацию, и это явно делает это специально, пропуская окончательный приращение.

Другой пост here упоминает, что приращения от итератора, когда он подключен к, скажем, cin, вызовет слишком много читает, так как чтение делается на operator++(). Это звучит как проблема с cin - почему это не прочитанное на operator*()?

Указывает ли это стандарт в любом месте? Документы я видел, не говоря уже, что происходит с от итератора, и я видел два разных страниц, которые дают «правильные возможные реализации», которые делают каждый из поведения:

At cppreference we have:

template< class InputIt, class Size, class OutputIt> 
OutputIt copy_n(InputIt first, Size count, OutputIt result) 
{ 
    if (count > 0) { 
     *result++ = *first; 
     for (Size i = 1; i < count; ++i) { 
      *result++ = *++first; 
     } 
    } 
    return result; 
} 

while at cplusplus.com we have:

template<class InputIterator, class Size, class OutputIterator> 
    OutputIterator copy_n (InputIterator first, Size n, OutputIterator result) 
{ 
    while (n>0) { 
    *result = *first; 
    ++result; ++first; 
    --n; 
    } 
    return result; 
} 

Как сделать п считывает и приводит к тем же содержанием в результате. Однако первый увеличит только «первый» итератор n-1 раз, а второй увеличит его n раз.

Что дает? Как написать переносимый код? Я могу использовать tellg, а затем seekg, но тогда я мог бы просто сделать цикл вручную (тьфу!).


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

auto pos = in_stream.tellg(); 
std::istreambuf_iterator<char> buf_iter(in_stream); 
std::copy_n(buf_iter, cl, sym.begin()); 

in_stream.seekg(pos + cl); 

uint64_t foo; 
in_stream.read(reinterpret_cast<char *>(&foo), 8); 

Кстати, в случае, если его не ясно, я стараюсь, чтобы избежать копирования данных в буфер, а затем снова в строку sym.


@DaveS: Двигаясь из моей конкретной проблемы, вот простая программа, которая не выводит то, что я бы ожидать в связи с тем, что входной итератор не увеличивается в последний раз:

#include <algorithm> 
#include <string> 
#include <iostream> 
#include <fstream> 

int main(int argc, const char * argv[]) 
{ 
    std::ifstream in("numbers.txt"); 

    std::istreambuf_iterator<char> in_iter(in); 
    std::ostreambuf_iterator<char> out_iter(std::cout); 

    std::copy_n(in_iter, 3, out_iter); 
    std::cout << std::endl; 

    std::copy_n(in_iter, 3, out_iter); 
    std::cout << std::endl; 

    std::copy_n(in_iter, 3, out_iter); 
    std::cout << std::endl; 

    return 0; 
} 

файл вход только "\n"

Я получаю:

012 
234 
456 

Из-за побочного эффекта istreambuf_iterator::operator++() это даст другой результат, если бы copy_n был реализован для увеличения итератора ввода n раз.


@aschepler: Необходимо, чтобы захватить локальный параметр, но я буду с ним:

std::generate_n(sym.begin(), cl, [&in_stream](){ return in_stream.get(); }); 
+2

Или как насчет: 'std :: generate_n (sym.begin(), cl, []() {return in_stream.get();});'? – aschepler

+1

Я спросил список рассылки для обсуждения в стандарте C++. Может быть, кто-то может объяснить это обоснование. Тема здесь: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/UdnfYg7HPjg – Brian

ответ

3

Источник итератор не принимается в качестве ссылки. Таким образом, его копия увеличивается в n раз, но параметр остается нетронутым.

9 из 10 раз, это то, что вы хотите.

Что касается побочных эффектов приращения конкретно на InputIterators обеспокоен я думаю официально, входные итераторы должны быть «приращение» при каждой операции чтения (повторяются для чтения без приращения делает не дают то же значение). Итак, просто сделайте приращение no-op.

+1

Хотя это верно, тип 'istreambuf_iterator' имеет неглубокую семантику копирования. Вопрос не в том, почему итератор не обновляется? сколько «почему итератор обновляется n - 1 раз, а не n раз?« – templatetypedef

+0

@templatetypedef ли вторая половина моего ответа имеет смысл для вас? (Я посмотрю, смогу ли я найти на этом достаточно авторитетный источник, но концептуально это имеет немного смысла). И я не думаю, что InputIterators имеют «Неглубокая семантика копии». У них есть недетерминированные результаты, что и есть то, что вы ожидаете с концепцией InputIterator. (Итератор по значению, но [«вы не можете переходить в один и тот же поток во второй раз "] (http://en.wikiquote.org/wiki/Heraclitus)) – sehe

+0

@sehe: Вы не можете использовать копию старой позиции входного итератора в целом, но' std :: istreambuf_iterator' - определяемый как позволяющий копиям указывать на один и тот же буфер, делая 'sbumpc' приращение и делая' sgetc' при разыменовании. – aschepler

7

n3797 [algorithms.general]/12

В описании алгоритмов операторов + и - используются для некоторых категорий итераторов, для которых они не должны быть определены. В этих случаях семантика a+n такая же, как и у

X tmp = a; 
advance(tmp, n); 
return tmp; 

и что из b-a является такой же, как

return distance(a, b); 

[alg.modifying.operations]

template<class InputIterator, class Size, class OutputIterator> 
OutputIterator copy_n(InputIterator first, Size n, 
         OutputIterator result); 

Эффекты: Для каждого неотрицательного целого i < n, выполняет *(result + i) = *(first + i).

Возвращает:result + n.

Сложность: Ровно n задания.


Я не уверен, что это хорошо сформированным для InputIterators (не многоходовых), так как она не изменяет исходный итератор, но всегда выдвигает копию исходного итератора. Это тоже неэффективно.

[input.iterators]/Таблица 107 - Входные требования итератора (в дополнение к Iterator)

Expression: Тип ++r
Возврат: X&
предварительно: r является разыменовываемыми.
post: r является разыскиваемым или r является мимоходом.
post: любые копии предыдущего значения r больше не требуются либо для того, чтобы быть разыменованным, либо быть в домене ==.

Насколько я могу видеть, a в

X tmp = a; 
advance(tmp, n); 
return tmp; 

не поэтому больше не требуется, чтобы быть incrementable.


соответствующий доклад дефекта: LWG 2173

+0

Я согласен, что это странно и должно быть дефектом библиотеки. Несколько других требований к алгоритму имеют одинаковую проблему. – aschepler

+0

Хотя я полностью согласен с наблюдением, я не вижу, как это относится к вопросу. Это правда, что 'std :: copy_n' делает недействительным входной итератор (и любую резервную копию, которая может быть у вас тоже). Это черная дыра. Но дело в том, что вызовы 'operator ++' следует рассматривать как наблюдаемое поведение (вы можете передать свои собственные). – MSalters

+1

@MSalters Не знаете, какова ваша критика. Я думаю, что спецификация 'copy_n' нарушена в отношении вызовов' operator ++ '. Нужно ли отвечать на вызовы N или N-1 на самом деле, потому что ни один из исполнителей, вероятно, не использовал спецификацию буквально. Если они это сделают, для 'i == n-1',' first' будет увеличиваться на 'n-1' раз, и после этого нет никакого приращения. Поэтому я * подозреваю * правильный ответ * N-1 *, но это зависит от того, как спецификация исправлена. – dyp

7

Причины, почему многие std::copy_n реализация увеличивает N-1 разы из-за взаимодействия с istream_iterator, и как это обычно реализуется.

Например, если у вас был входной файл с целыми числами в них

std::vector<int> buffer(2); 
std::istream_iterator<int> itr(stream); // Assume that stream is an ifstream of the file 
std::copy_n(itr, 2, buffer.begin()); 

Поскольку istream_iterator задается для чтения на приращение (и на любой конструкции или первого разыменованием), если std::copy_n увеличивается входной итератор 2 раза , вы фактически прочитали бы 3 значения из файла. Третье значение просто было бы отброшено, когда локальный итератор внутри copy_n вышел из сферы действия.

istreambuf_iterator не имеет то же взаимодействие, так как она фактически не копировать значения из потока в локальную копию, как и большинство istream_iterators сделать, но copy_n до сих пор ведет себя таким образом.

Редактировать: Пример потери данных, если экземпляр-N увеличился в N раз (описание cplusplus.com, которое не кажется правильным). Обратите внимание, что это действительно относится только к istream_iterators или другим итераторам, которые считывают и удаляют свои базовые данные при приращении.

std::istream_iterator<int> itr(stream); // Reads 1st value 

while(n > 0) // N = 2 loop start 
{  
*result = *first; 
++result; ++first; // Reads 2nd value 
--n; // N: 1 
// N = 1 loop start 
*result = *first; 
++result; ++first; // Reads 3rd value 
--n; // N :0 
// Loop exit 
} 
return result; 
+0

Я не думаю, что это правда - первое чтение выполняется до приращения, поэтому поскольку он стоит, этот эксперимент приведет к тому, что последний символ из первого чтения также будет первым символом из второго прочитанного, я думаю. В частности, вызов 'copy_n (input, 1, output)' в цикле привел бы к тому, чтобы он повторно записывал один и тот же (текущий) символ в вывод и никогда не вызывал 'operator ++()' на итераторе ввода. – sfjac

+0

@sfjac: Я передумал, чтобы удалить концепцию цикла (так как это смутило меня сразу после того, как я вернулся к ней). Если вы используете 'std :: istream_iterator ' и заставляете copy_n увеличивать входной итератор N раз, вы потеряете символ из вашего файла. Я могу подробно разобраться, если вы хотите –

+0

Интересное обоснование. Рассмотрим файл '1 2 hello'. Приведение итератора в два раза приведет к сбою и т. Д. – dyp

Смежные вопросы