Dump-sicheres Parsen



  • anti-freak schrieb:

    D.h. ihr beendet eure Programme, nur weil in einer Datei, die eingelesen wird, etwas nicht stimmt?

    Wer hat das geschrieben, und wo? Es hat denke ich auch keiner geschrieben, dass bei einem Parsefehler das komplette Programm abrauchen soll.

    Wie FreakY<3Cpp schon sagt muss man zwischen Nutzerfehlern und allgemeinen Systemfehlern unterscheiden. Bei Nutzereingaben muss ich dem user richtig auf die Finger gucken, dass er auch ja eine Zahl eintippt, wenn ich eine Zahl haben möchte usw. Dann muss ich dem Nutzer auch explizit sagen, was er falsch gemacht hat "Deine Schuhgröße muss eine Zahl sein!" Bei Systemfehlern ist das was ganz anderes. Dem Nutzer ist in keinster Weise geholfen, wenn ich an jeder Ecke Handler einbaue und ihm dann genau sagen kann, dass ich übers Netzwerk nur 22 byte bekommen habe, obwohl ich eigentlich 28 brauche. Da reicht es, wenn es einen allgemeinen "Netzwerk-Übertragungsfehler" gibt. Bei dem hier gegebenen Problem ist es so, dass der Input im Normalfall generiert und damit keine Nutzereingabe ist. Ein Fehler beim Parsen ist deshalb ein Systemfehler, kein Fehler in der Nutzereingabe. Ich brauch deshalb nicht bei jedem get, getline oder was auch immer eine Fehlerabfrage einbauen und dann am Ende ausspucken "Fehler beim Parsen in Zeile 21, Spalte 4, Zahl erwartet, Buchstabe gefunden" - es reicht ein allgemeiner "Fehler beim Parsen der Inputdatei". Ich muss nicht wissen, wann das failbit beim ifstream gesetzt worden ist, es reicht, wenn ich irgendwann merke, dass etwas schiefgelaufen ist, und dafür muss ich nicht den ganzen Parsercode mit Abfragen vollmüllen.
    Ja, der User kann am Input rumeditieren. Aber trotzdem ist das nicht der Zweck des Parsers, manuell erstellten Input sicher zu analysieren und zu parsen. Der User kann aber auch mitten während der Übertragung den Netzwerkstecker ziehen. Würdet ihr deshalb sowas schreiben?

    while (needMoreData())
    {
      if (!isStillConnectedToNetwork())
         throw DAUException("Plug that network cable back in, moron!");
      recv(sock, buf, len, flags);
      //...
    }
    


  • *hier stand nonsense*



  • also man kann aus einer fremdsoftware über zwischenablage eben eine datei im textformat exportieren. Die ist dafür gedacht ohne änderungen in meine software importiert zu werden. Wenn jemand etwas vollkommen anderes einfügt, wird das auch verstanden und ein fehler ausgegeben.
    Blöd ist nur, wenn mein programm eine bestimmte zeile in der mitte parst, in der normalerweise immer ein doppelpunkt vorkommt. Wenn nicht, gibt das jetzt probleme beim find und substr. Soll ich das prüfen?



  • Oder anders gefragt: Sollte man an jeder Stelle beim Parsen, bei jedem Zeichen und bei jeder Zeile damit rechnen, dass jeder noch so willkürliche Formatfehler auftritt, auch wenn der nur durch böswilligste Manipulation eintreffen kann?

    Einen Fehler auszugeben ist okay. Wenn aber find string::npos ergibt und ich das für substr nutze, ist das Verhalten vermutlich undefiniert. Das ist nicht Mal ein geordneter Crash. Andererseits hat der Benutzer dann wohl wirklich absichtlich versucht das Programm zu zerstören, das wäre also fast "gerecht".

    Aufwand ist es schon ein wenig. Ich muss halt bei jedem get, getline, find, substr prüfen, ob das Resultat auch einem korrekten Format entspricht. Und bei operator>> natürlich auch und das sind halt so meine Mittel.

    Trotzdem bereinige ich Derartiges wohl komplett, erscheint besser. 🙂



  • anti-freak schrieb:

    D.h. ihr beendet eure Programme, nur weil in einer Datei, die eingelesen wird, etwas nicht stimmt? O.o

    Exakt. Wenn es die Konfigurationsdatei ist, die die exakten Parameter enthält, die ich für eine Experimentalreihe ist, dann möchte ich unter überhaupt gar keinen Umständen, dass das Programm einfach mal macht, wenn fehler in der Datei sind. Es soll härtestmöglich crashen und weder kostbare Rechenzeit noch meine Zeit verschwenden.

    Und der User bekommt nichtmal ne Meldung, Na herzlichen dank... Ich finde das echt nicht schön. Fehlerbehebung muss ja nicht unbedingt sein, aber ne fehlermeldung is imo das mindeste.

    Wer sagt, dass wir das nicht haben? An irgendeiner Stelle kommt halt ein throw() das nirgenwo gefangen wird. Das Programm crasht und der nutzer erhält auf der Konsole noch den Inhalt von what(). Hier hat niemand von terminate gesprochen, nur davon, dass man nicht versuchen sollte, jeden möglichen Fehler zu korrigieren.



  • Eisflamme schrieb:

    Hoffe, jemand erkennt mein Problem

    Hallo Eisflamme,

    Dein Problem hast Du schon selber erkannt - nämlich:

    Eisflamme schrieb:

    ich habe jetzt einen gut funktionierenden Parser gebastelt. Ich bin jedoch mit der Sicherheit von Streams nicht sonderlich vertraut.

    Wenn Du konsequent Streams benutzt, so gibt es nur zwei Zustände, die hier eine Bedeutung haben. Der Stream ist im Fehlerzustand oder eben nicht. Im ersten Fall kannst Du anschließend beliebig viele Streamoperationen aufrufen - sie machen dann alle dasselbe - nämlich gar nichts.
    Damit dies funktioniert steht am Anfang jeder Streamoperation ein Objekt der Klasse std:istream::sentry, ist dies nicht ok, so wird jede Funktion sofort wieder verlassen. Und da crasht auch nichts - und ganz am Schluss fragt man dann den Stream, ob der Fehlerzustand gesetzt ist oder eben nicht. Im zweiten Fall ist alles gut.

    Eisflamme schrieb:

    Einen Fehler auszugeben ist okay. Wenn aber find string::npos ergibt und ich das für substr nutze, ist das Verhalten vermutlich undefiniert. Das ist nicht Mal ein geordneter Crash.

    Und wieder ein Grund, warum das Pärchen getline und Stringfrickelei ein Graus ist - lass es einfach sein. Mache Dich mit den Fähigkeiten von std::istream und Co vertraut. Die sind dafür gemacht - std::string ist es nicht.

    Eisflamme schrieb:

    Ich muss halt bei jedem get, getline, find, substr prüfen, ob das Resultat auch einem korrekten Format entspricht. Und bei operator>> natürlich auch ..

    Nein bei operator>> muss man eben nicht (s.o.) - man kann fröhlich weitermachen/-Lesen/-Parsen. Der Stream bleibt quasi einfach 'stehen', sobald ein Fehler auftritt.
    Und wenn Du einen geeigneten Streambuf 'einschiebst', der Zeichen und Zeilenumbrüche mit zählt, so kannst Du am Ende sogar genau sagen, in welcher Zeile und Spalte der Fehler aufgetreten ist.

    Und wenn ein Fehler auftritt sollte man alle Infos darüber an den werten User weitergeben und ihm die Entscheidung überlassen, was jetzt zu tun ist. Vielleicht hat er bloß die falsche Konfig-Datei ausgewählt - oder der Memory-Stick war nicht eingesteckt - oder das Laufwerk nicht gemountet - oder ... .

    Gruß
    Werner



  • Hallo,

    oh, okay, die Info, dass ein Stream nach Misslingen weiter benutzbar ist, ist mir neu und äußert wertvoll! Danke schon Mal dafür!

    Sagen wir, ich bin jetzt davon überzeugt, dass Streams toll sind (bin ich eh, aber ich kann damit noch nicht alles so lösen, wie ich will).

    Folgende zwei Probleme stellten sich mir. Wenn mir da jemand helfen könnte, wäre ich sehr froh und bräuchte find/substr wohl nicht mehr. 🙂

    Zu einem bestimmten Zeitpunkt in meinem Parsevorgang suche ich nur nach Zeilen, die mit "Beat" anfangen. Kommt keines vor, würde ich gerne den Rest der Zeile ignorieren (da hätte ich jetzt so was wie stream >> firstWord; if(firstWord != Beat) getline(stream, &dummy); gemacht)

    Dann möchte ich gerne in der aktuellen Zeile so viele Zeichen überspringen, bis das Zeichen ':' kommt. Der zu überspringende Teil soll auch Leerzeichen enthalten. Kommt jedoch kein ':' in der aktuellen Zeile vor, möchte ich das auch gerne irgendwie erfahren.

    Vermutlich braucht es da nur irgendein Schlüsselelement. Ich könnte auch eine Funktion schreiben, die einfach solange get aufruft, bis ein ':' kommt und eben throwt (o.ä. Fehlerbehandlung), wenn vorher ein '\n' gefunden wurde. Ist das die Lösung?

    Herzlichen Dank!



  • hola

    // von Werner geklaut und umgeschrieben
    template< char C >
    bool skip_to_char( std::istream& in )
    {
        char c;
        while( in >> c && c != C && c != '\n' && !in.eof());
        return c == C;
    }
    

    ungetestet, aber ungefaehr so koennte es aussehen

    Meep Meep



  • Ah, sehr gut sieht das aus. Das, was ich eben Schlüsselelement nannte, ist also im Endeffekt, dass man immer Zeichen pro Zeichen einliest. Das ist zwar kein get aber "stream >> c" ist ja so ähnlich (ist das nur syntaktisch ähnlich oder tatsächlich dasselbe?)

    Jedenfalls sollte ich damit alles lösen können, danke. 🙂 Falls noch jemand einen Nachtrag hat, gerne.



  • Meep Meep schrieb:

    // von Werner geklaut und umgeschrieben
    template< char C >
    bool skip_to_char( std::istream& in )
    {
        char c;
        while( in >> c && c != C && c != '\n' && !in.eof());
        return c == C;
    }
    

    ungetestet

    Hallo Meep Meep,

    ich bemühe mich immer, jeden Code Schnipsel, den ich hier rein stelle, auch zu testen. Weil - ich mache auch Fehler 😉 . Vielleicht hat Eisflamme inzwischen gemerkt, dass es nicht funktioniert, gerührt hat er sich ja noch nicht.

    Eisflamme schrieb:

    Das ist zwar kein get aber "stream >> c" ist ja so ähnlich (ist das nur syntaktisch ähnlich oder tatsächlich dasselbe?)

    'stream >> c' liest - so nichts anderes eingestellt ist - immer das nächste druckbare Zeichen. Oder anders ausgedrückt: alle White Space Character werden vorher überlesen. Der Zeilenvorschub '\n' gehört auch zu den White Space Characters und wird somit auch überlesen. Folglich ist die Bedienung "c != '\n'" immer erfüllt.

    Im Gegensatz dazu liefert isteam::get() jedes Zeichen, aber eben auch EOF. Deshalb ist der Return-Typ auch int und nicht char. Streng genommen müsste man das jedes mal prüfen bevor man es zum char konvertiert und vergleicht. Also etwa so:

    #include <iostream>
    #include <limits>
    
    template< char C >
    bool skip_to_char_in_line( std::istream& in )
    {
        typedef std::istream::traits_type Traits;
        for( std::istream::int_type m; (m = in.get()) != Traits::eof(); )
        {
            const char c = Traits::to_char_type( m );
            if( c == C )
                return true;
            if( c == '\n' )
                break;
        }
        return false;
    } 
    std::istream& skipline( std::istream& in )
    {
        return in.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
    }
    
    int main()
    {
        using namespace std;
        while( cin )
        {
            cout << "--> ";
            if( skip_to_char_in_line<':'>( cin ) )
            {
                int value;
                if( cin >> value )
                {
                    cin >> skipline; // Wichtig! Rest der Zeile überlesen
                    cout << "Gelesen: " << value << endl;
                }
                else
                    cerr << "Lesefehler" << endl;
            }
            else
                cout << "diese Zeile enthaelt kein ':'" << endl;
        }
        return 0;
    }
    

    ermöglicht diesen Dialog auf der Konsole:

    --> egal was : 42
    Gelesen: 42
    --> ohne Doppelpunkt
    diese Zeile enthaelt kein ':'
    --> mal sehn:123  rest der Zeile
    Gelesen: 123
    --> sdjksk sds
    diese Zeile enthaelt kein ':'
    --> :keine Zahl
    Lesefehler
    

    Gruß
    Werner



  • Eisflamme schrieb:

    Zu einem bestimmten Zeitpunkt in meinem Parsevorgang suche ich nur nach Zeilen, die mit "Beat" anfangen. Kommt keines vor, würde ich gerne den Rest der Zeile ignorieren (da hätte ich jetzt so was wie stream >> firstWord; if(firstWord != Beat) getline(stream, &dummy); gemacht)

    Könnte damit gehen:

    std::istream& search_beat( std::istream& in )
    {
        for( std::string token; in >> token && token != "Beat"; )
            in.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
        return in;
    }
    

    setzt aber voraus, dass sich hinter "Beat" garantiert ein Leerzeichen befindet. Falls nicht, macht man sich std::setw zu nutze.

    #include <istream>
    #include <limits>
    #include <string>
    #include <iomanip> // std::setw
    
    std::istream& search_beat( std::istream& in )
    {
        for( std::string token; in >> std::setw(4) >> token && token != "Beat"; )
            in.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
        return in;
    }
    

    Und falls es nicht immer "Beat" ist, kann man das Suchwort auch mitgeben.

    // includes wie oben
    struct search_text_at_beginline
    {
        explicit search_text_at_beginline( const std::string& key )
            : key_( key )
        {}
        friend std::istream& operator>>( std::istream& in, search_text_at_beginline st )
        {
            for( std::string token; in >> std::setw(st.key_.length()) >> token && token != st.key_; )
                in.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
            return in;
        }
    private:
        std::string key_;
    };
    

    Anwendung

    cin >> search_text_at_beginline("Beat")
    

    Eisflamme schrieb:

    Dann möchte ich gerne in der aktuellen Zeile so viele Zeichen überspringen, bis das Zeichen ':' kommt. Der zu überspringende Teil soll auch Leerzeichen enthalten. Kommt jedoch kein ':' in der aktuellen Zeile vor, möchte ich das auch gerne irgendwie erfahren.

    siehe Posting oben mit skip_to_char_in_line.

    Gruß
    Werner



  • Okay, das sieht super aus, herzlichen Dank. 🙂



  • Kann mir jemand erklären, warum Meep Meep skip_char als Template-Funktion deklariert hat? Was spricht dagegen den Buchstaben 'C' nicht einfach als Argument zu übergeben?



  • Qute schrieb:

    Kann mir jemand erklären, warum Meep Meep skip_char als Template-Funktion deklariert hat? Was spricht dagegen den Buchstaben 'C' nicht einfach als Argument zu übergeben?

    weil die Syntax eines Manipulators ohne Parameter vom Standard vorgegeben ist und keine weiteren Parameter außer std::istream& erlaubt. Gibt es eine Funktion

    std::istream& egal( std::istream& in );
    

    so kann diese Funktion mit operator>> an einen istream übergeben werden. Z.B.:

    std::cin >> egal; // ruft die Funktion egal mit std::cin als Parameter auf
    

    Möchte man Parameter übergeben muss man sich eine eigene struct oder class nebst eigenen inserter/extractor (also hier operator>>) bauen. So wie ich in meinem Posting oben - search_beat ist ohne Parameter; mit fest codiertem "Beat" und bei search_text_at_beginline kann man den Suchtext als Parameter mitgeben.

    Gruß
    Werner



  • Ah, auch das ist eine super Erkenntnis!

    Was, wenn ich mehrere Zeichen alternativ lesen möchte und rausfinden möchte, welche gelesen wurde? Ich brauche dann ja einen return type, der mir auch sagt, dass nichts gefunden wurde.

    Ich habe mir jetzt so was gebastelt, aber ich bin unsicher, welcher Wert Sinn macht, wenn es eben char ist. Ich könnte auch std::istream::int_type nehmen, dann müsste ich es aber jedes Mal konvertieren.

    template<char C1, char C2, char C3>
    	char skipToCharInLine(std::istream& in)
    	{
    		typedef std::istream::traits_type Traits;
    		for(std::istream::int_type m; (m = in.get()) != Traits::eof();)
    		{
    			const char c = Traits::to_char_type(m);
    			if(c == C1)
    				return C1;
    			if(c == C2)
    				return C2;
    			if(c == C3)
    				return C3;
    			if(c == '\n')
    				return '\n';
    		}
    		return 0; // eof
    	}
    

    Ist das so ok? Sonst fiele mir noch ein std::pair<bool, char> ein.

    Ach und eigentlich brauchen wir hier doch gar keine Templates, oder? Das ist ja kein Manipulator!

    Edit:
    Sorry, ich bin ein Nimmersatt und hier die Infos zu erfragen ist einfach so viel komfortabler als irgendwann wieder Code zu zeigen, den dann alle zerreißen. 🙂

    Ich möchte gerne auch chars skippen, die erst zur Laufzeit bekannt sind. Dann nehme ich so eine struct. Ich hätte aber gerne beides. Wie bezeichnet ihr das dann? Beides als skipChar geht ja nicht, aber mir fallen keine vernünftigen Bezeichner ein, außer so was wie: skipFixedChar und skipChar oder so, aber na ja



  • viellicht solltest du dir so langsam parser frameworks ansehen. boost::spirit oder boost::regex, je nachdem was du brauchst. Stringstreams sind für relativ einfach Datenformate geschrieben, nicht für komplexe Parseraufgaben.



  • Ja, das verstehe ich. Mein Datenformat ist aber gar nicht so komplex, wie man hier vermuten könnte. Ich hatte das ja in relativ kurzer Zeit schon mit stream + string gelöst. Ich will es nur noch auf Streams umbauen und dumpsicher machen, dann bin ich froh und glücklich.

    Zudem ist es eine großartige Übung. Selbst wenn man hier eleganter mit boost arbeiten könnte, so brauche ich die Streams doch ständig. Und ich verwende stets so ein String-Gefrickel. Das will ich mir jetzt ein für alle Mal abgewöhnen. Was ist dafür besser als eine recht komplexe Übung, die man am besten gar nicht erst mit Streams anfassen würde? 🙂

    Edit:
    Wenn ich zu viel frage, sagt mir das. Jetzt habe ich eine Zeile, in der ich prüfen will, ob sie mit einem Wort beginnt. Tut sie das nicht, möchte ich sie aber unversehrt lassen, d.h. das gelesene Wort nicht extrahieren. Ich muss also entweder einen ganzen String peeken (geht wohl nicht?), oder ich muss wieder etwas zurückstecken. Ich fand unget(), aber kann ich das beliebig häufig aufrufen? Dann müsste ja der Stream alles Vergangene noch beinhalten, löscht der das nicht?

    Folgender Code geht, aber das muss doch einfacher gehen?

    bool stringInLine(istream& in, const string& stringToFind)
    	{
    		typedef istream::traits_type Traits;
    		int charsExtracted = 0;
    
    		bool result = false;
    
    		for(Traits::int_type i; (i = in.get()) != Traits::eof();)
    		{
    			++charsExtracted;
    
    			char c = Traits::to_char_type(i);
    			if(c == '\n')
    				break;
    
    			const int charsExtractedBefore = charsExtracted;
    
    			if(c == stringToFind[0]) // first char fits!
    			{
    				string::const_iterator it;
    				for(it = stringToFind.begin() + 1; it != stringToFind.end(); ++it)
    				{
    					if((i = in.get()) == Traits::eof())
    						break;
    
    					++charsExtracted;
    
    					c = Traits::to_char_type(i);
    
    					if(*it != c)
    						break; // not correct word
    				}
    
    				if(it == stringToFind.end())
    				{
    					result = true;
    					break;
    				}
    
    				for(; charsExtracted > charsExtractedBefore; --charsExtracted)
    					in.unget();
    			}
    		}
    
    		for(; charsExtracted > 0; --charsExtracted)
    			in.unget();
    
    		return result;
    	}
    


  • Eisflamme schrieb:

    Wenn ich zu viel frage, sagt mir das.

    Hallo Eisflamme,

    frag' ruhig. Im schlimmsten Fall antwortet keiner mehr. Bei mir kann es auch mal vorkommen, dass ich erst zwei Tage später antworte.

    Eisflamme schrieb:

    Jetzt habe ich eine Zeile, in der ich prüfen will, ob sie mit einem Wort beginnt. Tut sie das nicht, möchte ich sie aber unversehrt lassen, d.h. das gelesene Wort nicht extrahieren.

    da kommen wir dann an die Grenzen des Streams. Garantiert ist nur, dass Du genau ein Zeichen weit nach vorn 'sehen' kannst.

    Eisflamme schrieb:

    Ich muss also entweder einen ganzen String peeken (geht wohl nicht?), oder ich muss wieder etwas zurückstecken. Ich fand unget(), aber kann ich das beliebig häufig aufrufen?

    Nein - unter Umständen kannst Du es nicht mal einmal aufrufen. Bei std::cin geht sicher eine Zeile und bei Dateien vielleicht die ganze Datei, aber zugesichert wird das nicht.

    Der Stream geht nach 'bad', wenn Du ungetc öfter aufrufst als der darunter liegende Streambuf es verträgt. Stell' dir vor es wäre eine RS232-Schnittstelle, die kann auf ungetc das gerade empfangene Zeichen nicht einfach zurück schicken.

    Das ist im Allgemeinen aber auch gar nicht nötig. Wenn die Zeichenfolge mit dem Schlüssel passt, darf sie auch überlesen werden und was soll denn passieren, wenn sie nicht passt? Wahrscheinlich willst Du dann auf eine andere Zeichenfolge prüfen oder?
    Stattdessen könntest Du auf mehrere Zeichenfolgen gleichzeitig prüfen - wäre das eine Lösung? Dazu muss man wieder nur ein Zeichen weit nach vorne schauen.

    Gruß
    Werner



  • Okay, danke fürs weitere Antworten 🙂

    Auf mehrere gleichzeitig, hmm... das könnte klappen.

    Es ist halt so: Entweder, es gibt ein gewisses Schlüsselwort in der Zeile, dann ist nur noch der Zeilenrest relevant, genau. Falls nicht, dann muss das erste Wort der Zeile extrahiert werden. Geht so was noch elegant oder sprengt das die Grenzen des "Schön Machbaren". Wobei ein getline in einen string auch nicht schön ist, wirkt nur irgendwie zweckmäßiger. Dann jedoch würde ich vermutlich einen zweiten Stream nutzen und den auf den string hetzen, nun ja...

    Und ist unget() für istream in derselben Zeile nun zugesichert oder nicht? Ich habe es jetzt so implementiert und es funktioniert, aber wenn das nicht sicher ist, begebe ich mich ja in Teufels Küche. Ich meine, der zu Grunde liegende string (keine Benutzereingabe und auch keine Datei) existiert ja noch komplett. Iteriert istream nicht einfach über irgendeine Zeiger/Iteratorkapselung durch den string?



  • Eisflamme schrieb:

    Was, wenn ich mehrere Zeichen alternativ lesen möchte und rausfinden möchte, welche gelesen wurde? Ich brauche dann ja einen return type, der mir auch sagt, dass nichts gefunden wurde.
    ...
    Sorry, ich bin ein Nimmersatt und hier die Infos zu erfragen ist einfach so viel komfortabler als irgendwann wieder Code zu zeigen, den dann alle zerreißen. 🙂

    Hi Eisflamme,

    stimmt das ist viel einfacher, aber weil heute irgendwie "Tag des Streams" ist, muss ich noch was loswerden.

    Werner Salomon schrieb:

    template< char C >
    bool skip_to_char_in_line( std::istream& in )
    {
        typedef std::istream::traits_type Traits;
        for( std::istream::int_type m; (m = in.get()) != Traits::eof(); )
        {
            const char c = Traits::to_char_type( m );
            if( c == C )
                return true;
            if( c == '\n' )
                break;
        }
        return false;
    }
    

    Der Code, den ich Dir ursprünglich posten wollte, sah völlig anders aus. Erst nach Meep Meeps Beitrag von 23:41:00 02.10.2012 bin ich auf diese Lösung umgeschwenkt.

    Wenn sich jetzt einer von den Performance-Junkies ranmacht und diesen Code einem Benchmark unterzieht, wird er wahrscheinlich feststellen, dass der ziemlich lahm ist. Das liegt daran, dass in einer Schleife Zeichen für Zeichen die relativ teure Methode 'istrem::get()' gerufen wird. Die ist deshalb relativ teuer, weil u.a. jedes mal so ein sentry-Objekt angelegt wird und überprüft wird, ob noch alle iO ist. Das habe ich Dir schon weiter oben erklärt (mein Beitrag 19:58:05 02.10.2012).

    deshalb lohnt es sich in solchen Fällen eine Stufe tiefer auf die streambuf-Ebene abzusteigen. Der streambuf stellt für die Zeichen-für-Zeichen-Analyse drei Methoden zur Verfügung.
    sgetc - liefert das aktuelle Zeichen, ohne Änderung des Lesezeigers
    snextc - schiebt den Lesezeiger um 1 nach vorn und liefert das dort gefunden Zeichen
    sbumpc - liefert das aktuelle Zeichen und schiebt danach den lesezeiger um 1 nach vorn
    Alle drei Methoden liefern einen int_type dessen Wert EOF sein kann und - falls nicht - erst in einen char konvertiert werden muss. Diese Konvertierung ist aber billig.

    Wenn man direkt auf den streambuf zugreift, muss man allerdings einiges selber machen. Dazu gehört zum einen das Setzen der Flags (fail, eof, bad) und das Abfangen von Exceptions.

    Dazu ein Beispiel, was Du oben angesprochen hast. Angenommen Du hast folgende Funktion zu implementieren:

    char search_in_line( std::istream& in, const std::string& chars );
    

    Diese Funktion soll alle Zeichen überlesen, bis sie entweder auf eines der Zeichen in 'chars' trift oder auf '\n' (EOL) oder eben auf EOF. Findet die Datei eines der Zeichen gibt sie es zurück in jedem anderen Fall ein char(0).

    Wie implementiert man das mit Zugriff aus den streambuf. Der Rahmen sieht immer so aus:

    char search_in_line( std::istream& in, const std::string& chars )
    {
        char result = char(0);
        std::istream::sentry ok( in, true ); // false := skip white space character
        if( ok )
        {
            // ... hier geht's weiter
        }
        return result;
    }
    

    Wie schon mehrfach erwähnt ist erst ein sentry-Objekt zu erzeugen. Das stellt sicher, dass der Stream noch good ist, und würde wir hier als 2.Paramter ein 'false' oder nichts (false ist default) übergeben, so würde das sentry-Objekt auch gleich alle White Space Character überlesen. Wichtig ist, dass im Ok-Fall die Methode istream::rdbuf() ein gültiges Streambuf-Objekt zurück liefert. Und das brauchen wir später.

    Vorher muss man sich aber noch um den Zustand kümmern.

    char search_in_line( std::istream& in, const std::string& chars )
    {
        char result = char(0);
        std::istream::sentry ok( in, true );
        if( ok )
        {
            std::ios_base::iostate state = std::ios_base::goodbit;
            try
            {
                // ... hier geht's weiter
            }
            catch(...)
            {
                state |= std::ios_base::badbit;
                if( in.exceptions() & std::ios_base::badbit )
                    throw;
            }
            in.setstate( state );
        }
        return result;
    }
    

    Für den Zustand des Streams wird am Anfang ein state auf good gesetzt. Kommt man jetzt an einen Punkt, bei dem eine Zustandsänderung notwendig wird, so wird nicht unmittelbar der Zustand des Streams geändert (istream::setstate) sondern nur die lokale Variable state verändert. Der Grund dafür liegt darin, dass mit jedem Setzen des Zustands eine Exception ausgelöst werden kann (s. ios_base::exceptions). Wirft der unterliegende konkrete Streambuf eine Exception, wrd diese zunächst gefangen und nur weitergegeben, wenn das badbit gesetzt ist. Ansonsten wird das badbit gesetzt - danach ist der streambuf unbrauchbar.
    Bis hier sind alle diese Lesefunktionen irgendwie gleich; z.B.: operator>>string müsste genauso aussehen.

    Und nach dem ganze Vorgeplänkel kommen wir jetzt zum eigentlichen Lesen. Da jedes Zeichen hier auf jeden Fall gelesen wird und kein gelesenes Zeichen später noch benötigt wird, reicht hier die Methode streambuf::sbumpc() (s.o.).

    char search_in_line( std::istream& in, const std::string& chars )
    {
        char result = char(0);
        std::istream::sentry ok( in, true );
        if( ok )
        {
            std::ios_base::iostate state = std::ios_base::goodbit;
            try
            {
                typedef std::istream::traits_type Traits;
                for(;;)
                {
                    const std::istream::int_type m = in.rdbuf()->sbumpc();
                    if( Traits::eq_int_type( m, Traits::eof() ) )
                    {
                        state |= std::ios_base::eofbit;
                        break;
                    }
                    const char c = Traits::to_char_type( m );
                    if( chars.find( c ) != std::string::npos )
                    {
                        result = c;
                        break;
                    }
                    if( c == '\n' )
                        break;
                }
            }
            catch(...)
            {
                state |= std::ios_base::badbit;
                if( in.exceptions() & std::ios_base::badbit )
                    throw;
            }
            in.setstate( state );
        }
        return result;
    }
    

    Das Lesen geschieht in Zeile 13. Zunächst wird auf EOF geprüft und auf EOF die Zustandsvariable verändert und die Schleife unterbrochen.
    Danach kann dann sicher nach char konvertiert werden (char_traits::to_char_type) und dann werden die einzelnen Checks durchgeführt.
    Fertig ist die Funktion.

    Eisflamme schrieb:

    Ich möchte gerne auch chars skippen, die erst zur Laufzeit bekannt sind. Dann nehme ich so eine struct. Ich hätte aber gerne beides. Wie bezeichnet ihr das dann? Beides als skipChar geht ja nicht, aber mir fallen keine vernünftigen Bezeichner ein, außer so was wie: skipFixedChar und skipChar oder so, aber na ja

    Vielleicht skip, search oder ignore mit entsprechenden Anhängen.

    Gruß
    Werner


Anmelden zum Antworten