istream::sentry

Einfach so einen Stream-Buffer lesen ist in std::istream nicht erlaubt. Die Operation wird immer durch einen istream::sentry geschützt.

Video

Quelltext

#include <istream>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

class my_ifstream : public basic_istream<char>
{
    basic_filebuf<char> m_file_buf;
    
public:
    
    my_ifstream(std::string const &name)
        : basic_istream(&m_file_buf)
    {
        m_file_buf.open(name, std::ios_base::in);
    }
    
    streamsize my_read(char *buffer, streamsize count)
    {
        sentry s(*this, true);
        if (s) {
            auto size = rdbuf()->sgetn(buffer, count);
            if (size < count) {
                setstate(ios::eofbit);
            }
            return size;
        }
        return 0;
    }
};

int main(int, char * argv[])
{
    my_ifstream ms { argv[1] };
    
    streamsize counter = 0;
    
    char buffer[100];
    do {
        counter += ms.my_read(buffer, 100);
    } while (ms);
    
    cerr << argv[1] << " enthält " << counter << " Bytes.\n";
}

Erklärung

Die Leseoperationen von std::istream und Verwandten werden immer mit einem std::istream::sentry geschützt. Dessen Aufgabe ist es, den Stream auf Fehler zu prüfen, ggf. verbundene Streams zu flushen und bei Bedarf Whitespaces aus dem Eingabestrom zu entfernen. Zu diesem Zweck erzeugen alle Leseoperationen ein sentry-Objekt (Zeile 22). Dessen Konstruktor nimmt zwei Parameter: den Stream, der zu schützen ist und einen bool, der anzeigt, ob Whitespaces zu entfernen sind oder nicht. Der zweite Parameter ist etwas ungewöhnlich, da er negiert arbeitet: wenn er true ist, dann werden die Whitespaces nicht entfernt. Ist er false (der Standardwert), dann liest der Konstruktor den übergebenen Strom so lang ein, bis die etwas ungleich Whitespace auftaucht. Die Klassifikation nach Whitespace oder nicht geschieht hierbei abhängig von der aktuell gesetzten Locale.

Schlägt eine der Initialisierungsaufgaben fehl, so setzt der sentry auf das Stream-Objekt das failbit und merkt sich diesen Zustand intern ebenfalls. Wandelt man ihn dann in einen bool um (Zeile 23), dann liefert er false. Laufen hingegen alle Initialisierungen erfolgreich durch, so liefert diese Umwandlung true und die eigentliche Leseoperation kann beginnen.

Die Klasse my_ifstream im Beispiel ist diesmal von std::istream abgeleitet. Damit stehen ihr natürlich die ganzen Fähigkeiten dieser Klasse zur Verfügung. So speichert istream bspw. einen Pointer auf den Lesepuffer (das streambuf-Objekt), auf den alle in istream implementierten Leseoperationen zugreifen. Dieser Zugriff geschieht mit der Methode rdbuf() (Zeile 24).

Wie schon beim letzten Mal nehmen wir an, dann der filebuf immer die gewünschte Menge an Bytes lesen kann, es sei denn, die Datei ist am Ende. Daher interpretieren wir den Fall "gelesene Bytes < angeforderte Bytes" als Ende der Datei und setzen mittels setstate() das entsprechende Fehlerbit im Stream.