Frage zu istream_iterator



  • Hallo,
    ich muss wieder feststellen das ich Iteratoren nicht wirklich verstanden habe...

    Es geht um den constexpr istream_iterator();, dieser erzeugt einen Streamende-Iterator. Nur woher weiß er auf welchen Stream sich das bezieht? Es wird im Konstruktor ja nichts angegeben. Unten ist auch ein Beispiel.

    Kann das jemand in ein paar Wörtern erklären? 😃

    Und: kann ich mir auch mit std::end(istream) einen ende-iterator holen?

    Edit: Hintergrund ist der Zugriff mit regex direkt auf den istream. Dann kann man sich doch das einlesen in string ersparen, oder?

    cu



  • Nur woher weiß er auf welchen Stream sich das bezieht?

    Muss er nicht wissen. Der "normale" Iterator "merkt" selbst wenn der sozusagen ans Ende läuft. Man könnte also auch einfach eine Funktion bool is_end(istream_iterator it) machen. Nur dass man damit nix anfängt wenn man generische Algorithmen hat die Iterator-Paare erwarten. D.h. man braucht einen End-Iterator. Also implementiert man den istream_iterator so, dass ein "ans Ende gelaufener" istream_iterator und ein default-konstruierter istream_iterator "equal" vergleichen, und schwupps, ist das Problem gelöst.

    Und: kann ich mir auch mit std::end(istream) einen ende-iterator holen?

    Nicht dass ich wüsste. Probier's doch einfach aus 🙂
    (Jaaaaa, ich weiss, nur weil etwas mit einem Compiler geht heisst nicht dass der Standard garantiert dass es überall geht. Aber in so einem Fall denke ich kann man davon ausgehen dass es nicht "zufällig" gehen wird bzw. dass auch keine Implementierung es als non-standard Erweiterung anbieten wird. Und abgesehen davon gibt es auch genügend "Online-Compiler" Seiten mit denen man den selben Source schnell mit verschiedenen Compilern ausprobieren kann.)



  • @hustbaer Hmm, damit ist meine Frage nicht so recht beantwortet. Im Beispiel wird einmal der istream_iterator-Konstruktor ohne Argument aufgerufen:

        std::istringstream stream("1 2 3 4 5");
        std::copy(
            std::istream_iterator<int>(stream),
            std::istream_iterator<int>(),
            std::ostream_iterator<int>(std::cout, " ")
        );
    

    Damit komme ich nicht klar. Ruft copy intern sowas wie istream.end() auf bzw. holt copy sich die Information um welchen istream es sich handelt aus dem ersten Argument? Ich glaube heute ist es zu spät für meine Birne. Morgen mal alles zu Iteratoren aus dem Breymann lesen. Gibt da ein paar Beispiele sehe ich gerade...

    Bei std::end(istream) währe es ja für mich einleuchtend...

    cu



  • @hustbaer Ok, vielleicht hab ich es doch begriffen. der end-iterator gibt also immer das selbe zurück, und wenn iterator und end-iterator gleich dann ende. Ja, morgen mehr lesen....



  • Ja. Der End-Iterator ist immer der selbe. copy weiss nix von irgendwas und macht da auch nix speziell. copy weiss nichtmal dass es überhaupt sowas wie nen istream_iterator gibt. copy weiss nur dass es kopiert, so lange seine Iterator "Laufvariable" und der übergebene end-Iterator "ungleich" vergleichen.

    Der "normale" (nicht-end) istream_iterator macht sich beim "++", wenn er sieht dass der Stream "eof" geworden ist, er also nichts mehr lesen konnte, sozusagen selbst zum End-Iterator. So dass er eben mit dem End-Iterator "gleich" vergleicht.

    Dass der einst "normale" istream_iterator dabei u.U. "vergisst" zu welchem Stream er gehört hat, ist dabei auch wörscht, da der ja "forward only" ist. D.h. er ist und bleibt "am Ende" und benötigt daher keinen Zugriff auf den Stream mehr.

    Also quasi (sinngemäss, GANZ grob skizziert):

    // in der istream_iterator implementierung, wird aufgerufen um das nächste Element zu holen
    void fetch_next() {
        (*m_stream) >> m_element;
        // ...
        if (m_stream->eof()) {
            // Stream is eof => make us an "end iterator"
            *this = istream_iterator();
            return;
        }
        // ...
    }
    


  • @hustbaer Ok, vielen dank, jetzt hab ich es begriffen. Trotzdem morgen mal mehr lesen...

    cu



  • Dass das bei anderen Iteratoren anders ist, d.h. dass man da nicht ohne Bezug auf die Collection einen End-Iterator erzeugen kann, liegt daran dass man der Implementierung die Möglichkeit geben möchte effizienteren Code zu erzeugen. z.B. kann man einfach ganz normale Zeiger als Iterator verwenden. Also z.B. zwei Zeiger auf Elemente des selben Arrays (bzw. einen "one past last element" Zeiger). Und dass ein nackiger Zeiger nicht wissen kann wo jetzt "Schluss" ist, sollte klar sein.



  • @hustbaer sagte in Frage zu istream_iterator:

    [...] Und dass ein nackiger Zeiger nicht wissen kann wo jetzt "Schluss" ist, sollte klar sein.

    Jep, das ist klar. Werde mich morgen etwas einlesen und hoffen das ich es in 2-3 Monaten nicht wieder vergessen habe...

    Was meinst du, regex direkt auf einen input-stream mit istreambuf_iterator (der liest scheinbar zeichenweise)? Mir gefällt das nicht erst alles in einen std::string zu tun. Oder sind die string-iteratoren wiederum effizenter das man das besser so macht?

    cu

    Edit: Zitat verrutscht...



  • Hachja, den Regex Teil hab ich ignoriert 🙂

    std::regex braucht doch bidirektionale Iteratoren, nicht? Also zumindest std::regex_match braucht bidirektionale Iteratoren, und es würde mich sehr sehr wundern wenn andere Regex-Funktionen mit weniger auskämen.

    D.h. Regex und istream_iterator geht sowieso nicht zusammen.

    Mir gefällt das nicht erst alles in einen std::string zu tun. Oder sind die string-iteratoren wiederum effizenter das man das besser so macht?

    Die String-Iteratoren sind vermutlich um mindestens ne Grössenordnung schneller als istream_iterator. Aber probier's doch einfach aus. Also z.B. zählen wie viele "x" du in einem File findest. Einmal per istream_iterator Schleife und einmal indem du das File (als ganzes oder auch Zeile für Zeile) in nen std::string liest, und dann mit new Iterator-Schleife über den String drübergehen. Und dann schreibst du mir wie die zwei Varianten sich schlagen (mit -O2 natürlich) 🙂



  • @hustbaer Jep, morgen (äh, heute) probier ich das...

    Stimmt, manchmal müssen die Automaten ja ein paar Zeichen zurück springen. Hmm, oder war es nur eins. Egal, alles später mal probieren...

    Vielen Dank



  • Ach und übrigens, für chars würde man üblicherweise eher nen istreambuf_iterator verwenden: https://en.cppreference.com/w/cpp/iterator/istreambuf_iterator

    Der is aber genau so ein "single pass input iterator", also auch nix mit std::regex.



  • @hustbaer Jep, hier ist die Übersicht, decrement fehlt 😕 shit happens...

    Edit: Selbst bei nur ein Byte zurück würde es nicht funktionieren wenn der Automat am Anfang einer Puffergrenze liegt. Lösung währe intern ein Puffer. Und dann ist man wieder gleich der Lösung mit std:.string. Ok...



  • @hustbaer Hallo, habe was zum testen geschrieben. Im Debug-modus ist die string-Variante so 5x schneller. Mit -O2 allerdings ist die istreambuf_iterator-Variante etwas schneller ungefähr gleich. Weiß aber nicht was da evtl. wegoptimiert wurde. Bei der string-variante habe ich einfach std::getline() genommen... Getestet habe ich mit std::count()...

    Ahh ja, alles auf einem alten Laptop.

    Release: Edit: g++-8 -c -pipe -pedantic -Wall -Wpedantic -Wextra -O2 -DRELEASE -std=c++17 -Wall -W ... mit libstdc++.so.6

    first start, create testfiles: test1.txt, test2.txt, Ok
    Test basic_string_iterator, duration: 1434µs, 1004µs, 950µs, 951µs, 1118µs, counting 5 * 100000 the character 'x'
    Test istreambuf_iterator,   duration: 1012µs, 985µs, 1078µs, 970µs, 961µs, counting 5 * 100000 the character 'x'
    
    und...
    
    Test basic_string_iterator, duration: 1406µs, 971µs, 946µs, 1141µs, 964µs, counting 5 * 100000 the character 'x'
    Test istreambuf_iterator,   duration: 1045µs, 1007µs, 1260µs, 1008µs, 1003µs, counting 5 * 100000 the character 'x'
    

    Debug: Edit: g++-8 -c -pipe -pedantic -Wall -Wpedantic -Wextra -g -DDEBUG -std=c++17 -Wall -W ...

    first start, create testfiles: test1.txt, test2.txt, Ok
    Test basic_string_iterator, duration: 9136µs, 9802µs, 8826µs, 16263µs, 12815µs, counting 5 * 100000 the character 'x'
    Test istreambuf_iterator,   duration: 51940µs, 41357µs, 41796µs, 41013µs, 41299µs, counting 5 * 100000 the character 'x'
    
    zweiter start...
    
    Test basic_string_iterator, duration: 9459µs, 8853µs, 8482µs, 8547µs, 8383µs, counting 5 * 100000 the character 'x'
    Test istreambuf_iterator,   duration: 47846µs, 41961µs, 40905µs, 40501µs, 40650µs, counting 5 * 100000 the character 'x'
    
    

    Testcode:

    // $Id: main.cpp 453 2018-09-25 15:31:53Z dirk $
    //
    // Test zwischen std::string-iterator und std::istreambuf_iterator
    // Es werden 2 Dateien mit *count* anzahl 'x'-Zeichen (plus linefeed)
    // erzeugt, falls nicht vorhanden. Die beiden iteratoren werden mit
    // std::count getestet indem die Anzahl 'x'-Zeichen gezählt wird.
    // insgesamt *rounds*-mal. Die Dauer wird in Microsekunden ausgegeben.
    
    #include <cstdlib>
    #include <iostream>
    #include <fstream>
    #include <string>
    #include <array>
    #include <iterator>
    #include <algorithm>
    #include <chrono>
    
    int main( int, char**)
    {
      int rounds=5;
      long count = 100000;
      std::array<std::string, 2> files{"test1.txt", "test2.txt"};
    
      { // braces for auto-destroying ifs1 & ifs2,
        // better using std::filesystem::exists(). if exists... :D
        std::ifstream ifs1(files.at(0));
        std::ifstream ifs2(files.at(1));
        if (!ifs1 || !ifs2)
        {
          std::cout << "first start, create testfiles: ";
    
          for( auto f: files)
          {
            std::cout << f << ", ";
            {
              std::ofstream of(f, std::ios_base::trunc);
              for(long i=0; i < count && of.good(); i++)
              {
                of << 'x';
              }
              of << '\n';
              if (of.bad()) exit (EXIT_FAILURE);
            }
          }
          std::cout << "Ok\n";
        }
      }
    
    
      long dur=0;
      count=0;
    
      std::cout << "Test basic_string_iterator, duration: ";
      for(int i=0; i<rounds; ++i)
      {
        {
          std::ifstream ifs(files.at(0));
    
          auto start = std::chrono::high_resolution_clock::now();
    
          std::string s;
          if (!std::getline(ifs, s))
            exit (EXIT_FAILURE);
    
          count = std::count(s.begin(), s.end(), 'x');
    
          auto ende  = std::chrono::high_resolution_clock::now();
          dur = std::chrono::duration_cast<std::chrono::microseconds>(ende-start).count();
        }
        std::cout << dur << "µs, ";
      }
      std::cout << "counting " << rounds << " * " << count
                << " the character 'x'\nTest istreambuf_iterator,   duration: ";
      for(int i=0; i<rounds; ++i)
      {
        {
          std::ifstream ifs(files.at(1));
    
          auto start = std::chrono::high_resolution_clock::now();
    
          count = std::count(std::istreambuf_iterator<char>(ifs),
                                  std::istreambuf_iterator<char>(), 'x');
    
          auto ende  = std::chrono::high_resolution_clock::now();
          dur = std::chrono::duration_cast<std::chrono::microseconds>(ende-start).count();
        }
        std::cout << dur << "µs, ";
      }
    
      std::cout << "counting " << rounds << " * " << count << " the character 'x'\n";
    
      return EXIT_SUCCESS;
    }
    

    Aber vielleicht habe ich was falsch gemacht. habe extra 2 Dateien genommen und versucht die Ergebnisse zu benutzen damit nichts wegoptimiert wird.

    cu



  • @dirkski Wenn ich den std::string vorher passend reserviere wird es schon besser...

    first start, create testfiles: test1.txt, test2.txt, Ok
    Test basic_string_iterator, duration: 891µs, 604µs, 584µs, 714µs, 589µs, counting 5 * 100000 the character 'x'
    Test istreambuf_iterator,   duration: 1049µs, 1022µs, 1047µs, 1013µs, 1004µs, counting 5 * 100000 the character 'x'
    
    zweiter durchlauf...
    
    Test basic_string_iterator, duration: 932µs, 653µs, 592µs, 592µs, 590µs, counting 5 * 100000 the character 'x'
    Test istreambuf_iterator,   duration: 1085µs, 1159µs, 1052µs, 1049µs, 1051µs, counting 5 * 100000 the character 'x'
    

    cu



  • @dirkski Nö das könnte schon passen. Sieht gut aus 👍
    istreambuf_iterator hat auch definitiv das Potential ordentlich viel schneller als istream_iterator zu sein. Oder sagen wir so: wenn istreambuf_iterator nicht ordentlich schneller ist, dann macht die Implementierung ordentlich was falsch 😉
    Wäre also interessant wie istream_iterator im Vergleich dazu aussieht.

    Und ich gehe davon aus dass bei der getline Variante ein nicht unwesentlicher Teil der Zeit in getline draufgeht. Wäre also u.U. interessant die Zeit für getline einzeln zu messen. Und/oder noch ne Messung mit istream::read zu machen wo du gleich das ganze File auf einmal einliest.



  • @hustbaer sagte in Frage zu istream_iterator:

    @dirkski Nö das könnte schon passen. Sieht gut aus 👍
    istreambuf_iterator hat auch definitiv das Potential ordentlich viel schneller als istream_iterator zu sein. Oder sagen wir so: wenn istreambuf_iterator nicht ordentlich schneller ist, dann macht die Implementierung ordentlich was falsch 😉
    Wäre also interessant wie istream_iterator im Vergleich dazu aussieht.

    Na, ist uninteressant. Der ist ja für formatierte Eingabe und benutzt operator>>

    @hustbaer:

    Und ich gehe davon aus dass bei der getline Variante ein nicht unwesentlicher Teil der Zeit in getline draufgeht. Wäre also u.U. interessant die Zeit für getline einzeln zu messen. Und/oder noch ne Messung mit istream::read zu machen wo du gleich das ganze File auf einmal einliest.

    Jep. std::getline() muss ja jedes Zeichen auf das Trennzeichen prüfen...

    Es ging mir ja ursprünglich darum den std::istreambuf_iterator für regex zu benutzen. Aber das geht ja eh nicht 😕

    Zum Hintergrund: Es ging um das Posting „Name für neues Objekt aus Stream auslesen?“. Der normale Ablauf, Zeilenweise lesen und dann die Zeile aufdröseln ist ja eigentlich doppelte Arbeit. Erst geht std::getline() den Text durch und prüft jedes Zeichen auf newline (oder eof) und danach geht man die Zeile nochmal durch und testet jedes Zeichen auf Komma zum aufsplitten. Da ich schon immer Spaß mit regulären Ausdrücken hatte (perl) und die Suchautomaten sehr effizient sind dachte ich man kann den stream mit einem Rutsch aufdröseln. Naja, da der Automat erst zur Laufzeit gebaut wird lohnt sich das wohl eh nicht.

    Die Variante mit istream::read werde ich aber noch testen. Datei einlesen muss man ja eh, Nachteil ist halt das es bei sehr großen Dateien theoretisch knallen kann und das es dauert bis alles geladen ist.

    cu

    Edit: Zitat korriegiert korrigiert



  • @hustbaer Was hälst du von der Idee einen Bidirektionalen istreambuf_iterator zu schreiben? Der müsste ja einfach die alten Puffer sammeln statt zu verwerfen. Dann klappt's auch mir den regex-Funktionen.

    Als Übung werde ich das evtl. am Wochenende mal beginnen.

    Edit: Man müsste prüfen ob der stream seekable ist. Dann kann man zurück ohne Buffersammeln. Wenn nicht (std::cin z.B.) sammeln. Hmm...

    cu



  • So lange nicht die Chance besteht dass es knallt wegen out-of-memory, is immer noch das beste alles in einem Rutsch einzulesen. Dann haste deine BiDi Iteratoren und alles ist gut. Alternativ gäbe es Memory-Mapping, dummerweise ist das plattformabhängig. (Gibt's da ne gute portierbare Dings, Boost oder so? Keine Ahnung.)

    Ansonsten, wenn du bloss CSV File parsen willst oder etwas ähnlich einfaches... der dafür nötige Automat ist recht schnell selbst geschrieben. Dann kannst du die Daten blockweise lesen (z.B. 1 MB Blöcke) und blockweise durch den Automaten stopfen.

    Was hälst du von der Idee einen Bidirektionalen istreambuf_iterator zu schreiben? Der müsste ja einfach die alten Puffer sammeln statt zu verwerfen.

    Ich hab von dem ganzen iostreams Jedöns zu wenig Ahnung um dazu fundiert was sagen zu können, aber ich stelle es mir aufwendig vor.

    Edit: Man müsste prüfen ob der stream seekable ist. Dann kann man zurück ohne Buffersammeln.

    Uiuiui, du willst ganz sicher nicht jedes mal wenn die Regex meint hüpfen zu müssen den Stream seeken. Ausserdem musst du noch bedenken dass du bei "normalen" (nicht "single pass input") Iteratoren auch unabhängige Iterator-Kopien unabhängig und korrekt behandeln musst. D.h. du müsstest vor dem Lesen jedes Zeichens nochmal den Stream-Offset prüfen und... also ne, ich würde das nicht implementieren wollen. Und performant is vermutlich auch was anderes.


    Falls du einfach nur gute Performance mit wenig Aufwand willst, dann würde ich sagen probier's erstmal mit getline. Guck erstmal wie viele MB/s du damit hinbekommst und ob das reicht. Und falls es dir langsam vorkommt guck noch in welchen Funktionen wie viel Zeit draufgeht (Profiler) - nicht dass der Flaschenhals am Ende noch das Parsen der bereits in den std::string geladenen Daten ist und du fängst an was zu optimieren was gar nicht so bremst.

    Falls es dir natürlich darum geht dich damit zu spielen und dazuzulernen ist das was anderes, in dem Fall mach ruhig und probiere & teste wozu du lustig bist. Trotzdem würde ich auch da empfehlen dir auch die einfache Lösung anzusehen, damit du nen Vergleich hast.



  • @hustbaer Ok ok. Dachte zum üben in Sachen iteratoren... Aber dann fange ich besser mit einfacheren an. Im Breymann ist was zum lesen.

    Für diesen CSV-artigen Zeilenaufbau aus dem Post reicht natürlich getline().
    Aber ich habe auch an eine Hilfsklasse gedacht die die >>-operatoren überlädt (Und umgekehrt zum schreiben). Irgendwer hat mir mal (möglicherweise du) vor längerer Zeit zu solch einer Klasse geraten im Zusammenhang mit dem lesen von BMP-Dateien. Ist bestimmt ein Jahr her. Jemand wollte BMP-Dateien aufdröseln und ich hatte da auch was in der Richtung probiert. Moment... such ...

    // $Id: binaryreader.h 335 2017-11-29 01:42:03Z dirk $
    //
    
    #ifndef BINARY_IFSTREAM_H
    #define BINARY_IFSTREAM_H
    
    #include <cstdint>
    #include <istream>
    #include <type_traits>
    
    namespace zeugs {
      namespace iohelper {
    
        // Hilfsklasse um die Operatorfunktion>> für integrale Datentypen
        // auf binäres Verhalten zu ändern. Mit z.B. bstream >> intVar
        // werden sizeof(intVar) Bytes gelesen und nach intVar geschrieben.
        // Awendungsbeispiel:
        // int intVar;
        // BinaryReader br( std::ifstream( "test.bmp", std::ios::binary|std::ios::in ) )
        // br >> intVar;
    
        class BinaryReader
        {
          std::istream &is;
        public:
          BinaryReader(std::istream &is) : is(is) { }
    
          // Operator>> nur für integrale Typen:
          template <class T, class = std::enable_if<std::is_integral<T>::value> >
          BinaryReader & operator>> (T& value)
          {
            is.read(reinterpret_cast<char*>(&value), sizeof(T));
            return *this;
          }
    
          inline std::istream & get_stream()
          {
            return is;
          }
        };
     }
    }
    

    Tja, sowas als csv_helper. Kann man dann noch den delimiter setzen oder auch quote-Zeichen. Dann einfach:

    csv_helper csvh(std::cin);
    
    std::string text;
    int ganzzahl;
    float gk_zahl;
    
    csvh >> text >> ganzzahl >> gk_zahl;
    

    Bei Eingabe: foobar, 42, 3.14 (Problem, Text darf kein Komma enthalten)

    Oder:

    csv_helper csvh(std::cin);
    csvh.setDelim(";");
    csvh.setQuote("\"\"");
    
    std::string text;
    int ganzzahl;
    float gk_zahl;
    
    csvh >> text >> ganzzahl >> gk_zahl;
    

    Eingabe: "foobar"; "42"; "3.14"

    Sowas in der art...

    Ich wollte ja noch die istream::read()-Version testen. Nur wie finde ich die Dateigröße heraus bei einem geöffneten ifstream. std::filesystem::file_size geht hier glaub nicht (zumindest funktioniert ...::exists() nicht). Hab zwar einen g++-8 aber noch die libstdc++ V6 😕 Wegen Datei an einem Stück einlesen. Bei meinen Testdateien kenne ich die Größe ja. Aber so generell...

    cu



  • Grösse rausbekommen ist leider wirklich lästig. Was geht ist ans Ende zu springen und sich dann die Position zu holen, und danach wieder zurückzuspringen - seekg/tellg. Scheint "common practice" zu sein. Dass es dazu keine "direkte" C oder C++ API gibt ist doof. Vermutlich dem Umstand geschuldet dass es auch bei den FILE* Funktionen nix vergleichbares gibt. Andrerseits könnte es das OS, zumindest für "normale" Files...


Log in to reply