Eigenes getline?



  • @SeppJ: beeindruckend! 🙂


  • Mod

    theta schrieb:

    @SeppJ: beeindruckend! 🙂

    Alles bei Werner Salomon gelernt 🙂 .



  • Ich verstehe alles außer underflow und uflow. (die wohl overflow heißen sollte?)
    Kann mir das jemand erklären? 😞



  • 314159265358979 schrieb:

    Ich verstehe alles außer underflow und uflow. (die wohl overflow heißen sollte?)
    Kann mir das jemand erklären? 😞

    Nein, overflow() ist etwas anderes (das spielt bei den Ausgaben vor). Beide Funktionen werden verwendet, wenn der interne Zwischenspeicher zu Ende ist, wobei sich ein paar Randbedingungen unterscheiden, wann welche benötigt wird.


  • Mod

    314159265358979 schrieb:

    Ich verstehe alles außer underflow und uflow. (die wohl overflow heißen sollte?)
    Kann mir das jemand erklären? 😞

    Das wie und warum dazu findest du wiederum hier:
    http://www.cplusplus.com/reference/iostream/streambuf/
    Da sehen wir erst einmal oben die public-Member. Diese haben wir geerbt. Die verwalten irgendwie intern einen Puffer (ist ja ein Streambuf 😉 ) und stellen dem Stream ein Interface zur Verfügung. Der Stream wird nun diese Funktionen benutzen, um aus dem Puffer zu lesen. Und jetzt sieht du vielleicht schon, warum das Underflow heißt: Wenn der interne Puffer (unser Puffer!) leer gelesen ist, dann müssen neue Zeichen rein. Die Standardimplementierung dieser Funktionen (die wir tunlichst nicht überschreiben sollten!) ruft dann die passenden virtual-Memberfunktionen auf. underflow, wenn der Puffer neue Zeichen braucht und overflow wenn der Puffer voll ist (im Falle eines ostreams).
    Es gibt auch eine Reihe weiterer virtueller Funktionen, aber wenn du die Beschreibungen liest, wirst du sehen, dass diese eine brauchbare Defaultimplementierung haben und einfach wieder die anderen Memberfunktionen aufrufen. Die wichtigen Funktionen sind die, die ich überschrieben habe (und overflow für Ausgabestreambufs), die per Default überhaupt nichts tun, außer Fehlerwerte zurück zu geben. Für so einen einfachen Filterstream wie diesen reicht das völlig aus. Wenn's etwas anspruchsvoller sein soll, kann man sich die anderen Funktionen auch mal näher ansehen.

    So, nun wissen wir aber gar nicht, woher wir unsere Zeichen bekommen sollen. Und selbst wenn wir es wüssten, dann würde ich ungern die Funktionen zum Lesen aus Dateien nachbauen. Daher habe ich mir den alten Streambuf gemerkt. Da braucht man gar nicht wissen, was da genau dahinter steht. Dessen public-Memberfunktionen funktionieren für uns genau so gut wie für den ursprünglichen fstream. Wenn also jemand unseren Streambuf nach Zeichen fragt und unser interner Puffer leer ist, dann fragen wir einfach den alten Strambuf nach Zeichen und filtern diese. Was danach damit passiert oder wie das intern gehandhabt wird kann uns egal sein. Wir sitzen mit unseren virtuellen Funktionen an der Schlüsselstelle, wo jeder vorbei muss.

    P.S.: Möglich, dass der Default-Streambuf gar keinen internen Puffer hat. Die Beschreibung von setbuf klingt irgendwie danach. Ich bin nicht so der Experte auf dem Gebiet. Egal, der Filebuf den wir intern benutzen wird sicherlich richtig gepuffert sein.

    P.P.S.: Danke für die Frage, ich glaube ich habe das Gesamtkonzept gerade selber das erste Mal richtig verstanden. Ursprünglich stand hier eine ganz andere Erklärung wie ich es mir vorher vorgestellt habe, bis ich bei genauerem Nachdenken gemerkt habe, dass das gar nicht stimmen konnte.



  • Danke für die Erklärung, wird schon etwas klarer 🙂



  • Hey ho,

    sauber SeppJ, vielen Dank, das hat mir schon enorm weitergeholfen. 🙂

    Ich habe jetzt folgenden Fall: Es kann sein, dass '\r' alleine vorkommt, '\n' alleine vorkommt oder '\r' nebst '\n' vorkommt. Ich muss also weg von der Einzeichen-Basis.

    So wie ich das sehe, lässt sich das folgendermaßen umsetzen:
    - '\r' nebst '\n' löse ich, indem ich nach Lesen von '\r' schaue, ob das nächste Zeichen '\n' ist und, falls ja, den get-Zeiger einfach weiterschiebe und '\n' zurückgebe
    - '\r' ohne '\n' wird gelöst, indem ich beim Lesen von '\r' einfach '\n' zurückgebe und den get-Zeiger normal lasse (einfache Konvertierung)
    - '\n' wird als normales Zeichen zurückgegeben

    Dann lässt sich das normale getline wieder anwenden. Ist folgender Code wohl in Ordnung?

    custom_streambuf::int_type custom_streambuf::underflow()
    {
    	int_type c = oldBuf.sgetc();
    
    	if(traits_type::eq_int_type(c, 13)) // c == '\r'
    	{
    		// Are characters to read and is next one '\n'?
    		if(in_avail() == 0)
    			return traits_type::int_type(10); // '\n';
    		int_type c2 = oldBuf.snextc(); // move pointer to next element
    
    		if(traits_type::eq_int_type(c2, 10))
    			return c2;
    
    		// here: c2 != '\n', so we should move back
    		oldBuf.sungetc();
    	}
    
    	// In case of '\n' or other digits, we can return just them
    	return c;
    }
    
    custom_streambuf::int_type custom_streambuf::uflow()
    {
    	int_type c = oldBuf.sbumpc();
    
    	if(traits_type::eq_int_type(c, 13)) // c == '\r'
    	{
    		// We are at the next character; is it '\n'?
    		int_type c2 = oldBuf.sgetc();
    
    		if(traits_type::eq_int_type(c2, 10)) // it is '\n', so we should move to next token but return '\n'
    		{
    			snextc();
    			return c2;
    		}
    
    		// here: c2 != '\n', so this get-pointer place is just right
    	}
    
    	// In case of '\n' or other digits, we can return just them
    	return c;
    }
    

    Ich bin nicht ganz sicher, ob dieses in_avail genau macht, was ich möchte, laut Doku liest es ja die Anzahl der noch zu lesenden Zeichen, auch falls der interne Puffer gerade leer ist, aus. Wenn das also >0 ist, kommt noch was, ansonsten ist wohl eof | fail der Fall.

    Mein initiales Beispiel "Hallo Du!" funktioniert damit übrigens. Seht ihr irgendwelche Fehler?


  • Mod

    Ich würde nicht mit in_avail() arbeiten. Das ist oftmals so implementiert, dass es einfach 0 liefert, weil der Streambuf gar nicht wissen kann, wie viel noch kommt (woher soll er z.B. wissen wie viel der Anwender noch tippen wird, wenn er an der Tastatur hängt?). Was du tun könntest, wäre, auf EOF zu prüfen (siehe z.B hier wie das geht). Aber ich würde das etwas simpler machen: Du hast eine Klasse, die kann auch einen Zustand haben. Wenn '\r' kommt, setzt du dir einen bool-Member, und wenn '\n' kommt, dann guckst du ob der bool gesetzt ist und verwirfst das Zeichen gegebenenfalls.

    Ungetestet:

    class CRLF_filter : public std::streambuf
    {
    public:
      CRLF_filter(std::istream& in)
        : in(in) 
        , buf (in.rdbuf())  
      {
        in.rdbuf(this);
      }
      ~CRLF_filter()
      {
        in.rdbuf(buf); 
      }
    
    protected:
      virtual int_type underflow() 
      {
        int_type c = buf->sgetc();
        if (traits_type::eq_int_type( c, LF ) && last_was_CR)
         {
          int_type c = buf->snextc();  // Nächstes Zeichen nehmen
          last_was_CR = false;      
         }
        if (traits_type::eq_int_type( c, CR ))
         {
           c = LF;
           last_was_CR = true;
         }
        return c; 
      }
      virtual int_type uflow()    
      {
        int_type c = buf->sbumpc();
        if (traits_type::eq_int_type( c, LF ) && last_was_CR)
         {
          int_type c = buf->sbumpc();  // Man beachte den Unterschied zu oben
          last_was_CR = false;      
         }
        if (traits_type::eq_int_type( c, CR ))
         {
           c = LF;
           last_was_CR = true;
         }
        return c; 
      }
    private:
      char_filter( const char_filter& ); 
      char_filter& operator=( const char_filter& ); 
    
      std::istream& in;
      std::streambuf* buf;
      static const int_type CR, LF;
      bool last_was_CR;
    }; 
    
    CRLF_filter::int_type CRLF_filter::LF  = CRLF_filter::traits_type::to_int_type('\n');
    CRLF_filter::int_type CRLF_filter::CR = CRLF_filter::traits_type::to_int_type('\r');
    

    So, ich hoffe, ich habe da keine Dummheiten gemacht, die man erst bei Sonderfällen wie \r\EOF oder ähnlichem bemerkt.



  • Hast Recht, das ist eigentlich viel einfacher und v.a. sicherer. 🙂

    Aber wenn \r\EOF endet, gilt last_was_CR; wenn ich die nächste Datei lese und die mit \n anfängt, wird das \n ignoriert. Können wir das noch irgendwie lösen?

    Hm... wobei ich gerade überlege, ob dieser Fall überhaupt eintritt, ohne dass wir ein neues istream-Objekt haben..


  • Mod

    Eisflamme schrieb:

    Hm... wobei ich gerade überlege, ob dieser Fall überhaupt eintritt, ohne dass wir ein neues istream-Objekt haben..

    Theoretisch könnte der Nutzer das Filterobjekt auch an einen anderen Stream binden, aber da wir keine Schnittstelle zur Verfügung stellen, kann er den Streambuf der unter unserem Stream liegt nicht ändern. Theoretisch könnte er diesen manipulieren, aber wer so etwas tut, soll auch mit den Konsequenzen leben. Das ist schließlich ein absichtlicher Hack. Und es wäre selbst dann auch konsistent: Der Streambuf filtert \r zu \n und \r\n zu \n. Woher die Zeichen kommen, sollte unserem Filter egal sein.

    Jedenfalls brauchst du keine Angst zu haben, dass dir das bei normaler Benutzung passiert.



  • Ok, prima. 🙂

    Ich habe jetzt ein paar Tests gemacht. Hier 5 Dateien:

    48 61 6C 6C 6F 0A 44 75 21
    48 61 6C 6C 6F 0D 44 75 21
    48 61 6C 6C 6F 0D 44 75 0D 0A
    48 61 6C 6C 6F 0D 44 75 0A
    48 61 6C 6C 6F 0D 44 75 0D
    

    (\r \n in der Mitte hatte ich vorher schon Mal getestet, das geht auch)

    Ich erzeuge jeweils einen neuen istream und auch Buffer. Für diese fünf Dateien funktioniert alles. 🙂👍

    Ein paar Kleinigkeiten habe ich im Code noch geändert, weil z.B. das lastCR-Flag bei normalen Zeichen nach \r nicht geändert wurde und so. Zur Vollständigkeit nochmal der Code:

    custom_streambuf::int_type custom_streambuf::underflow()
    {
    	int_type c = oldBuf.sgetc();
    
    	if(traits_type::eq_int_type(c, LF) && lastWasCR) // \r\n -> we are at \n
    	{
    		lastWasCR = false;
    		// Ignore this
    		return oldBuf.snextc();
    	}
    	if(traits_type::eq_int_type(c, CR)) // \r -> we memorize this but return \n
    	{
    		lastWasCR = true;
    		return LF;
    	}
    	lastWasCR = false;
    
    	return c;
    }
    
    custom_streambuf::int_type custom_streambuf::uflow()
    {
    	int_type c = oldBuf.sbumpc();
    
    	if(traits_type::eq_int_type(c, LF) && lastWasCR) // \r\n -> we are at \n
    	{
    		lastWasCR = false;
    		// Ignore this
    		return oldBuf.sbumpc();
    	}
    	if(traits_type::eq_int_type(c, CR)) // \r -> we memorize this but return \n
    	{
    		lastWasCR = true;
    		return LF;
    	}
    	lastWasCR = false;
    
    	return c;
    }
    


  • Hallo Eisflamme, hallo SeppJ,

    wenn man eine (Text-)Datei im sogenannten DOS-Format vorliegen hat - also mit dem "\r\n" als Zeilenende, dann ist für die Auflösung dieses Zeichenpaars IMHO die std::codecvt-Facette zuständig.

    Das geht im Prinzip so:

    #include <fstream>
    #include <iostream>
    #include <locale>
    #include <string>
    
    class ConvertCrLf2Lf : public std::codecvt< char, char, int >
    {
    protected:
        virtual bool do_always_noconv() const
        {
            return false;
        }
        // -- Einlesen
        virtual result do_in( state_type& state,
            const extern_type* from, const extern_type* from_end, const extern_type*& from_next,
            intern_type* to, intern_type* to_limit, intern_type*& to_next ) const
        {
            const char CR('\r');
            const char LF('\n');
            to_next = to;
            for( from_next = from; from_next != from_end && to_next != to_limit; ++from_next )
            {
                switch( state )
                {
                case 0: // normal
                    if( *from_next == CR )
                        state = 1; // wir merken uns nur, dass CR da war
                    else
                        *to_next++ = *from_next;
                    break;
    
                case 1: // das vorherige Zeichen war CR
                    if( *from_next != LF )
                    {
                        *to_next++ = CR; // allein stehendes CR nachliefern
                        if( to_next == to_limit )
                            return std::codecvt_base::partial;
                    }
                    if( *from_next != CR )
                    {
                        *to_next++ = *from_next;
                        state = 0;
                    }
                }
            }
            return from_next == from_end? std::codecvt_base::ok: std::codecvt_base::partial;
        }
        // -- Ausgeben
        // virtual result do_out(stateT& state,
        //   const intern_type* from, const intern_type* from_end, const intern_type*& from_next,
        //   extern_type* to, extern_type* to_limit, extern_type*& to_next) const { ...
    };
    
    int main()
    {
        using namespace std;
        ifstream in("input.txt"/*, ios_base::binary*/ );  // s.u. bei Windows
        in.imbue( locale( in.getloc(), new ConvertCrLf2Lf ) );
        for( string line; getline( in, line ); )
            cout << "> " << line << endl;
        return 0;
    }
    

    Die Facette kann mit imbue an den Stream und damit an den Streambuf übergeben werden. Jeder basic_filebuf sollte auch was damit anfangen können. Bei Windows-Systemen ist das aber nie notwendig; dort reicht es die Datei im Textmode zu öffnen - d.h. ohne das binary (s.o.). Wenn man es unter Windows ausprobieren will, so muss die Datei binär geöffnet werden und - ganz wichtig - VC10 benutzen, da die älterne Versionen einen Fehler haben - siehe codecvt<char,char>-Problem.

    Habe i.A. nicht so viel Zeit, noch mehr in die Tiefe zu gehen; mehr zum Thema hatten wir im Forum schon mal hier.

    Gruß
    Werner


Anmelden zum Antworten