std::istream_iterator mit copy_n() und Freunden

Lesezeit: 7 Minuten

Das Snippet unten liest drei ganze Zahlen aus std::cin; es schreibt zwei in numbers und verwirft die dritte:

std::vector<int> numbers(2);
copy_n(std::istream_iterator<int>(std::cin), 2, numbers.begin());

Ich würde erwarten, dass der Code genau zwei ganze Zahlen ausliest std::cin, aber es stellt sich heraus, dass dies ein korrektes, standardkonformes Verhalten ist. Ist das ein Versehen im Standard? Was ist der Grund für dieses Verhalten?


Ab 24.5.1/1 im C++03-Standard:

Nach der Konstruktion und jedes Mal, wenn ++ verwendet wird, liest und speichert der Iterator einen Wert von T.

Im obigen Code liest der Stream-Iterator also zum Zeitpunkt des Aufrufs bereits eine ganze Zahl. Von diesem Punkt an ist jeder Lesevorgang des Iterators im Algorithmus ein Read-Ahead, der den zwischengespeicherten Wert des vorherigen Lesevorgangs liefert.

Der neueste Entwurf der nächsten Norm, n3225, scheint hier keine Änderung zu tragen (24.6.1/1).

In einer diesbezüglichen Anmerkung, 24.5.1.1/2 des aktuellen Standards in Bezug auf die istream_iterator(istream_type& s) Konstruktor liest

Effekte: Initialisiert in_stream mit
s. value kann während der Konstruktion oder beim ersten Verweis initialisiert werden.

Mit Betonung auf “value kann initialisiert werden …” im Gegensatz zu “soll initialisiert werden”. Das klingt widersprüchlich zu 24.5.1/1, aber vielleicht verdient das eine eigene Frage.

stdistream iterator mit copy n und Freunden
dex schwarz

Leider hat der Implementierer von copy_n das Vorauslesen in der Kopierschleife nicht berücksichtigt. Die Visual C++-Implementierung funktioniert sowohl für stringstream als auch für std::cin wie erwartet. Ich habe auch den Fall aus dem ursprünglichen Beispiel überprüft, in dem der istream_iterator in Reihe konstruiert ist.

Hier ist der Schlüsselcode aus der STL-Implementierung.

template<class _InIt,
    class _Diff,
    class _OutIt> inline
    _OutIt _Copy_n(_InIt _First, _Diff _Count,
        _OutIt _Dest, input_iterator_tag)
    {   // copy [_First, _First + _Count) to [_Dest, ...), arbitrary input
    *_Dest = *_First;   // 0 < _Count has been guaranteed
    while (0 < --_Count)
        *++_Dest = *++_First;
    return (++_Dest);
    }

Hier ist der Testcode

#include <iostream>
#include <istream>
#include <sstream>
#include <vector>
#include <iterator>

int _tmain(int argc, _TCHAR* argv[])
{
    std::stringstream ss;
    ss << 1 << ' ' << 2 << ' ' << 3 << ' ' << 4 << std::endl;
    ss.seekg(0);
    std::vector<int> numbers(2);
    std::istream_iterator<int> ii(ss);
    std::cout << *ii << std::endl;  // shows that read ahead happened.
    std::copy_n(ii, 2, numbers.begin());
    int i = 0;
    ss >> i;
    std::cout << numbers[0] << ' ' << numbers[1] << ' ' << i << std::endl;

    std::istream_iterator<int> ii2(std::cin);
    std::cout << *ii2 << std::endl;  // shows that read ahead happened.
    std::copy_n(ii2, 2, numbers.begin());
    std::cin >> i;
    std::cout << numbers[0] << ' ' << numbers[1] << ' ' << i << std::endl;

    return 0;
}


/* Output
1
1 2 3
4 5 6
4
4 5 6
*/

  • +1 Danke! Ich war mir sicher, dass das Problem nur in ist std::istream_iterator, und dort sollte es behoben werden. Ich frage mich jedoch, ob diese Änderung der Algorithmen nur ein eigentümliches Verhalten berücksichtigt std::istream_iterator führt die Dinge unnötig kompliziert ein. Ich meine, wenn wir jetzt _n()-Algorithmen mit Eingabeiteratoren schreiben, müssen wir immer das Vorauslesen berücksichtigen. Es ist wichtig, sich daran zu erinnern, wenn es nicht gelöst werden kann std::istream_iterator.

    – wilhelmtell

    28. Februar ’11 um 0:13

  • Äh. Ich denke, das Problem liegt nicht in istream_iterator, sondern in der Implementierung von copy_n in der von Ihnen verwendeten STL. Aus diesem Grund habe ich den Code für copy_n bereitgestellt, damit Sie Ihren überprüfen können.

    – dex schwarz

    28. Februar ’11 um 10:45


Heute bin ich auf ein sehr ähnliches Problem gestoßen, und hier ist das Beispiel:

#include <iostream>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <string>

struct A
{
    float a[3];
    unsigned short int b[6];
};

void ParseLine( const std::string & line, A & a )
{
    std::stringstream ss( line );

    std::copy_n( std::istream_iterator<float>( ss ), 3, a.a );
    std::copy_n( std::istream_iterator<unsigned short int>( ss ), 6, a.b );
}

void PrintValues( const A & a )
{
    for ( int i =0;i<3;++i)
    {
        std::cout<<a.a[i]<<std::endl;
    }
    for ( int i =0;i<6;++i)
    {
        std::cout<<a.b[i]<<std::endl;
    }
}

int main()
{
    A a;

    const std::string line( "1.1 2.2 3.3  8 7 6 3 2 1" );

    ParseLine( line, a );

    PrintValues( a );
}

Das Kompilieren des obigen Beispiels mit g++ 4.6.3 erzeugt eines:

1.1 2.2 3.3 7 6 3 2 1 1

, und das Kompilieren mit g++ 4.7.2 führt zu einem anderen Ergebnis:

1.1 2.2 3.3 8 7 6 3 2 1

Der c++11-Standard sagt dies über copy_n :

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

Effekte: Für jede nicht negative ganze Zahl i < n wird *(Ergebnis + i) = *(erste + i) ausgeführt.
Rückgabe: Ergebnis + n.
Komplexität: Genau n Aufgaben.

Wie Sie sehen, ist nicht angegeben, was genau mit den Iteratoren passiert, was bedeutet, dass es implementierungsabhängig ist.

Meiner Meinung nach sollte Ihr Beispiel den 3. Wert nicht lesen, was bedeutet, dass dies ein kleiner Fehler im Standard ist, dass das Verhalten nicht angegeben wurde.

1641744943 376 stdistream iterator mit copy n und Freunden
Bo Person

Ich kenne die genaue Begründung nicht, aber da der Iterator auch operator*() unterstützen muss, muss er die gelesenen Werte zwischenspeichern. Dies wird vereinfacht, wenn dem Iterator erlaubt wird, den ersten Wert bei der Konstruktion zwischenzuspeichern. Es hilft auch bei der Erkennung des Endes des Streams, wenn der Stream anfänglich leer ist.

Vielleicht ist Ihr Anwendungsfall einer, den das Komitee nicht berücksichtigt hat?

  • @BoPersson std::istream_iterator ist ein Eingabeiterator, garantiert also nur einen einzigen Durchgang. Sobald der Benutzer einen Eingabeiterator dereferenziert, liegt es in seiner Verantwortung, ihn zwischenzuspeichern. Mit anderen Worten, für einen Eingabeiterator i, der Ausdruck *i==*i hält nicht unbedingt.

    – wilhelmtell

    23. Februar ’11 um 1:36

  • Ich denke, dies könnte eine LWG-Ausgabe wert sein: Wie oft darf copy_n zuerst inkrementiert werden? n-mal? Oder n-1 mal (vorausgesetzt n ist positiv)? Hier finden Sie Anweisungen zum Einreichen eines LWG-Problems: open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#submit_issue .

    – Howard Hinnant

    23. Februar ’11 um 1:55

  • @wilhelmtell Du nervst überhaupt nicht, das ist ziemlich interessant. :-). Für mich bedeutet Single Pass, dass Sie den Iterator nur einmal inkrementieren können, nicht, dass Sie ihn nicht mehr als einmal dereferenzieren können. Ein Vorwärts-Iterator ist ein Multi-Pass, in dem Sie eine Kopie speichern und von vorne beginnen können, indem Sie jedes Element mehrmals besuchen.

    – Bo Person

    23. Februar ’11 um 6:25

  • @sbi: Wenn die Anweisungen unter diesem Link nicht ausreichen, biete ich meine Dienste an, um interessierten Parteien bei der Einreichung eines Problems zu helfen. Ich glaube, Sie können mich privat erreichen, indem Sie einfach auf meinen Namen klicken. Wenn das jedoch nicht hilft, suchen Sie einfach nach “Howard Hinnant”. Ich bin nicht schwer zu finden. Der C++-Standardisierungsprozess besteht aus Freiwilligen. Und wir brauchen mehr davon. Das Einreichen eines Problems ist eine hervorragende Möglichkeit, einen Beitrag zu leisten.

    – Howard Hinnant

    27. Februar ’11 um 1:40

  • @wilhelmtell: Nur zur Info, dies ist das Commit-Log, das Ihren positiven Einfluss auf libc++ zeigt: listen.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20110221/…

    – Howard Hinnant

    27. Februar ’11 um 23:08

Heute, 9 Jahre nach dir, bin ich in das gleiche Problem geraten. Als ich diesem Thread folgte und mit dem Problem spielte, ist dies aufgefallen: Es scheint, dass wir den Iterator nach dem ersten Mal für jede Lesung einen Schritt durchlaufen können (ich meine cin kann auch das Zeilenende nicht automatisch ignorieren, wir helfen dabei cin.ignore(), wir können auch dieser Implementierung helfen, denke ich):

    #include<bits/stdc++.h>
    using namespace std;

    int main(){

    freopen("input.txt","r",stdin);

    istream_iterator<int> it(cin);

    ostream_iterator<int> cout_it(cout, " ");

    copy_n(it, 5, cout_it);

    cout<<"nAnd for the rest of the streamn";

    for(int i=0;i<10;i++){

        it++;

        copy_n(it, 1, cout_it);

      }

    return 0;
   }

und das sollte eine Ausgabe erzeugen wie:

1 2 3 4 5
And for the rest of the stream
6 7 8 9 10 11 12 13 14 15

.

224400cookie-checkstd::istream_iterator mit copy_n() und Freunden

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy