[T] Aufbau eines Streambuffers



  • Vorbemerkungen

    Wie alle Bestandteile der C++ Standardbibliothek kann auch die IOStream-Bibliothek beliebig erweitert werden. Auf der einen Seite lässt sich die Verarbeitung neuer und bestehender Datentypen durch Überladung der Streaming-Operatoren (und durch Definition neuer Locales) anpassen, aber auf der anderen Seite kann auch das Ziel der Stream-Operationen angepasst werden.

    Diese Anpassung erfolgt, indem eine neue Streambuffer-Klasse definiert und in das bestehende Grundgerüst eingearbeitet wird. Wie genau das erfolgen kann, werde ich hier an einem Beispiel erklären.

    Inhalt

    1. Grundlagen
    2. Ausgabe
    3. Eingabe
    4. Verwaltung
    5. Verbindung zum Stream
    6. Generische Erweiterung

    1 Grundlagen

    Der Ausgangspunkt der gesamten Arbeit ist die Klasse basic_streambuf<>, bzw. ihre Spezialisierungen streambuf (für char-Streams) und wstreambuf (für wchar_t-Streams). Um in die Stream-Bibliothek eingearbeitet zu werden, muß die eigene Klasse von dieser abgeleitet werden:

    template<typename charT,typename traitT=std::char_traits<charT> >
    class mybuf : public std::basic_streambuf<charT,traitT>
    {
      ...
    };
    

    Bei allen Aufgaben der Streams gilt volle Arbeitsteilung. Der Stream kümmert sich selber darum, seine Eingabedaten in Byte-Folgen zu konvertieren bzw. zu parsen (genauer gesagt, überlässt er diese Aufgabe seinem Locale), der Streambuffer arbeitet nur mit diesen Bytefolgen und braucht (und darf) sich nicht um ihre innere Struktur zu kümmern. Dieses Vorgehen erleichtert die Entwicklung eines Streambuffers erheblich, weil der Puffer nichts von der logischen Struktur der verarbeiteten Daten wissen muß.

    Der nächste wichtige Punkt ist die Frage, woher der Stream seine Daten bekommen soll. Diese Frage verlässt gewöhnlich den Rahmen der C++ Bibliothek und nutzt plattformspezifische Funktionen zum Lesen und Schreiben von Bytefolgen. Zur Vereinfachung werde ich hier die Funktionen man: fread und man: fwrite aus der C Standardbibliothek verwenden.

    Anmerkung: Ich weiß, daß C++ mit den fstream's das selbe leistet, aber hier geht es darum, das Prinzip der Zusammenarbeit zwischen Stream und Puffer zu demonstrieren.

    Für meine ersten Ausführungen werde ich mich außerdem darauf beschränken, mit char-Daten zu arbeiten (Kapitel 6 fasst zusammen, was für die Verwendung mit beliebigen Zeichenklassen angepasst werden muß). Damit ergibt sich als Grundgerüst des Puffers:

    #include <cstdio>    //fread(), fwrite() etc.
    #include <iosfwd>    //Streamdeklarationen
    #include <streambuf> //Definition des Streambuffers
    
    class cfbuf : public std::streambuf
    {
    private:
      FILE* m_file;
    public:
      //...
    }
    

    2 Ausgabemethoden

    Die Ausgabe von Daten ist einfacher, weil hier weniger Funktionen beteiligt sind. Der Stream kann zwei Methoden des Puffers nutzen, um Daten auszugeben - sputc(c) schreibt ein einzelnes Zeichen, sputn(s,n) schreibt n Zeichen ab Position s.

    Beide Methoden schreiben (soweit vorhanden) ihre Parameter in einen internen Speicherbereich. Dieser basiert auf drei Zeigern, die von der Basisklasse verwaltet werden:

    • pbase() - Anfang des Puffers (put base)
    • pptr() - aktuelle Position (put pointer)
    • epptr() - Ende des Puffers (end put pointer)

    sputc() vergleicht die Werte pptr() und epptr() und ruft bei Gleichheit die Methode overflow() auf, andernfalls wird lediglich des angekommene Zeichen an pptr() eingetragen und dieser weitergeschoben. sputn() ruft in einer Schleife sputc() für jedes Zeichen seiner Eingabe auf.

    Die Methode overflow() ist damit der Punkt, an dem die eigene Ausgabeverwaltung ansetzen sollte. Diese Methode erhält das letzte Zeichen übergeben, das nicht mehr in den verfügbaren Speicher gepasst hat, und gibt entweder dieses Zeichen selber oder den End-of-File Wert (als Kennzeichnung für aufgetretene Fehler) als Antwort zurück, nachdem sie den Zwischenspeicher und den übergebenen Wert geschrieben hat.

    2.1 ungepufferte Ausgabe

    Im einfachsten Fall verzichtet man auf eine interne Pufferung und schreibt jedes ankommende Zeichen direkt auf den Ausgabekanal. Dazu werden die Ausgabezeiger des Puffers per Default alle mit NULL initialisiert - der Puffer hat keinen eigenen Zwischenspeicher, also führt jeder Schreibversuch zu einem Aufruf von overflow().

    protected:
      virtual int_type overflow (int_type c)
      {
        if(c!=EOF)
        {
          char z=c;
          if(fwrite(&z,1,1,m_file)!=1)
            return EOF;
        }
        return c;
      }
    

    Mit dieser Methode lässt sich der Puffer schon einsetzen, aber es bestehen noch Optimierungsmöglichkeiten. Die Methode sputn() ruft zunächst die virtuelle Methode xsputn() auf, die standardmäßig n mal sputc() aufruft, um jedes Zeichen einzeln zu schreiben. Dieses Verfahren lässt sich abkürzen, indem man die virtuelle Methode überschreibt, um direkt zu schreiben:

    virtual std::streamsize xsputn(const char* s,std::streamsize num)
    {
      return fwrite(s,1,num,m_file);
    }
    

    2.2 gepufferte Ausgabe

    Alternativ ist es auch möglich, die Eingaben intern zwischenzuspeichern und von vornherein in größeren Blöcken weiterzugeben. Dazu muß neben der eigentlichen Eingabe auch eine Synchronisation des Streams (z.B. über flush()) verarbeitet werden - dies geschieht in der virtuellen Methode sync().

    Um einen Zwischenspeicher für die Ausgabe einzurichten, kann die Methode setp(b,e) verwendet werden. Diese Methode erhält zwei Parameter, mit denen sie pbase() und pptr() bzw. epptr() initialisiert. Eine weitere Hilfsfunktion namens pbump() dient dazu, pptr() im laufenden Betrieb anzupassen - sie bekommt einen Größenangabe und schiebt den Schreibzeiger entsprechend vor (oder auch zurück).

    private:
      static const int obuf_size = 10;
      char m_obuf[obuf_size+1];
    
    public:
      cfbuf(FILE* dfile) : m_file(dfile)
      {
        setp(m_obuf,m_obuf+(obuf_size));
      }
      ~cfbuf()
      {
        sync();
      }
    
    protected:
      //eigentliche Speicherung
      int flushbuf()
      {
        int num = pptr()-pbase();
        if(fwrite(m_obuf,1,num,m_file) != num)
          return EOF;
        }
        pbump(-num);//Lesezeiger zurücksetzen
        return num;
      }
    
      virtual int_type overflow(int_type c)
      {
        if(c!=EOF)
        {
          *pptr() = c;
          pbump(1);
        }
        if(flushbuf() == EOF)
          return EOF
        return c;
      }
    
      virtual int sync()
      {
        return flushbuf() == EOF ? -1 : 0;
      }
    

    Anmerkung: Bei dieser Implementation ist der tatsächlich verfügbare Zwischenspeicher um ein Feld größer als der offizielle Speicher. Auf diese Weise kann overflow() auch das Zeichen noch unterbringen, das für den Überlauf gesorgt hat. Daß der Schreibzeiger dabei über das Ende des "offiziellen" Speichers hinauslaufen kann, stört nicht weiter.

    In diese Implementation lässt sich auch die Methode xsputn() von oben einbauen, allerdings sollte diese vor dem Schreiben ebenfalls flushbuf() aufrufen, bevor sie ihre eigene Eingabe schreibt:

    virtual std::streamsize xsputn(const char* s,std::streamsize num)
    {
      if(flushbuf()!=EOF)
        return fwrite(s,1,num,m_file);
      else
        return EOF;
    }
    

    3 Eingabenmethoden

    Die Verwaltung der Eingabe wird etwas komplexer zu bewerkstelligen, weil der Stream deutlich mehr Möglichkeiten hat, auf die Daten seines Puffers zuzugreifen. Die Methoden sgetc(), sbumpc() und snextc() geben jeweils einzelne Zeichen zurück und verschieben teilweise den Lesezeiger, sgetn(s,n) liest Zeichenketten und sputbackc(c) und sungetc() schreiben Zeichen zurück in den Eingabekanal. Alle diese Operationen arbeiten normalerweise auf dem selben internen Speicher und verschieben einige Hilfszeiger, um ihre Position zu kennzeichnen.

    Genau wie der Zwischenspeicher für die Ausgabe wird auch der Eingabespeicher mit Hilfe von drei Zeigern verwaltet:

    • eback() - Anfang des Speichers, Ende der Putback-Area (end back)
    • gptr() - aktuelle Position (get pointer)
    • egptr() - Ende des Speichers (end get pointer)

    sbumpc() und sgetc() liefern jeweils das aktuelle Zeichen - wenn der Puffer komplett gelesen wurde, wird vorher underflow() aufgerufen, um ihn neu auffüllen zu können. sbumpc() schiebt zusätzlich gptr() weiter - dazu verwendet sie die zweite Hilfmethode uflow(), die zunächst underflow() aufruft und anschließend den Lesezeiger weiterschiebt. sgetn() arbeitet analog zu sputn() und holt sich jedes geforderte Zeichen einzeln über sbumpc() - dieses Verhalten kann angepasst werden, indem zusätzlich die Methode xsgetn() überschrieben wird.

    Der Lesespeicher wird initialisiert, indem die Methode setg() aufgerufen wird. Diese erhält drei Parameter, mit denen die drei Steuerzeiger initialisiert werden. Dabei muß der Schreibzeiger nicht unbedingt am Anfang des Speichers starten (vor der Startposition wird noch Platz für die Putback-Area (s.u.) benötigt).

    3.1 gepufferte Eingabe

    Wenn ein interner Speicher bereitgestellt wird, reicht es aus, underflow() zu überschreiben, andernfalls muss auch uflow() angepasst werden (siehe im nächsten Abschnitt). Außerdem müssen natürlich die Zeiger in den Zwischenspeicher initialisiert werden.

    private:
      static const int ibuf_size = 10;
      char m_ibuf[ibuf_size];
    public:
      cfbuf(FILE* dfile) : m_file(dfile)
      {
        // setze Lesepuffer (Startpunkt, Position, Endpunkt)
        setg(m_ibuf+4,m_ibuf+4,m_ibuf+4);
      }
    protected:
      virtual int_type underflow()
      {
        if(gptr()<egptr())
          return *gptr();//noch Daten im Puffer vorhanden
    
        // Größe der Putback-Area ermitteln
        int nput = gptr() - eback();
        if(nput>4) nput = 4;
        // und Putback-Area umkopieren
        memcpy(m_ibuf+(4-nput),gptr()-nput,nput);
    
        // neue Zeichen lesen
        int num = fread(m_ibuf+4,1,ibuf_size-4,m_file);
        if(num<=0) return EOF;
    
        // setze Lesepuffer neu
        setg(m_ibuf+(4-nput),m_ibuf+4,m_ibuf+4+num);
    
        return *gptr();
      }
    

    Wie man sieht, ist underflow() etwas komplizierter aufgebaut als seine Entsprechung overflow(). Die Methode muß zunächst überprüfen, ob der Lesepuffer überhaupt leer ist (wenn nicht, kann sie sofort zurückkehren). Anschließend müssen die letzten vier gelesenen Zeichen an den Anfang des Speichers kopiert (für eventuelle putback()-Aufrufe) und der Rest des Puffers mit neuen Eingabezeichen gefüllt werden.

    *new* Zeichen, die per sputpack() und sungetc() zurückgeschrieben worden, werden jeweils in die Putback-Area des Puffers geschrieben - der Bereich zwischen den Hilfszeigern eback() und gptr() (im obigen Quellcode ist hierfür ein Platz von 4 Byte vorgesehen). Wenn der Platz nicht mehr ausreicht, wird die Methode pbackfail() aufgerufen, die entweder einen Fehler melden oder die Putback-Area vergößern kann. Für den Moment reicht das Standardverhalten aus, deshalb verzichte ich hier auf eine Spezialbehandlung.

    3.2 ungepufferte Eingabe

    Natürlich kann man auch bei der Eingabe auf einen Zwischenspeicher verzichten und jedes angeforderte Zeichen direkt abholen. Allerdings ist dieser Ansatz komplizierter, da neben underflow() auch die Methoden uflow() und pbackfail() überschrieben werden müssen. Letztere ist wichtig, weil der Stream laut ANSI Standard mindestens ein Zeichen zurückschreiben können muß ohne einen Fehler auszulösen.

    char m_last;
    virtual int_type uflow()
    {
      return m_last=fgetc(m_file);
    }
    
    virtual int_type underflow()
    {
      return m_last;//*???*
    }
    virtual int_type pbackfail(int_type c=EOF)
    {
      return ungetc(c!=EOF?c:m_last,m_file);
    }
    

    4 Verwaltungsaufgaben

    Ein Teil der Verwaltungsfunktionen werden ebenfalls vom Stream an seinen Puffer weitergeleitet. Auf diese Weise können alle Verwaltungsaufgaben zentralisiert werden.

    4.1 Positionierung

    Streams positionieren sich in ihrem Datenstrom, indem sie die Methoden pubseekpos() und pubseekoff() des angehängten Puffers aufrufen. Diese leiten die Aufrufe weiter an die virtuellen Methoden seekoff() und seekpos(), die für eine eigene Verarbeitung überschrieben werden können.

    //Um"rechnung" von seekdir in C Basisadressen
    int getbase(ios_base::seekdir b)
    {
      switch(b)
      {
      case ios_base::beg : return SEEK_SET;
      case ios_base::cur : return SEEK_CUR;
      case ios_base::end : return SEEK_END;
      }
    }
    
    virtual pos_type seekoff(off_type off,ios_base::seekdir way,ios_base::openmode which = ios_bas::in|ios_base::out)
    {
      fseek(m_file,off,getbase(way);
      return ftell(m_file);
    }
    
    virtual pos_type seekpos(pos_type sp,ios_base::openmode which = ios_base::in|ios_base::out)
    {
      return seekoff(sp,ios_base::beg,which);
    }
    

    ~Ich bin mir nicht sicher, ob seekpos so funktioniert - pos_type ist (laut MSDN) ein struct und vermutlich nicht zu long int konvertierbar~

    4.2 Dateiverwaltung

    Der Stream-Buffer verwaltet selber die Dateistruktur, über die er liest bzw. schreibt. Da liegt es nahe, ihm auch die volle Kontrolle über diese Struktur zu überlassen. Da ein iostream keine Vermutungen über die internen Daten seines Puffers anstellt, gibt es auch keine standardisierte Schnittstelle für diesen Zweck, deshalb können die benötigten Methoden beliebig benannt werden:

    private:
      //Um"rechnung" von openmode in C Zugriffsmodi
      string getmode(ios::openmode m)
      {
        string bin = m & ios::binary ? "b" : "";
        switch(m & ~ios::binary & ~ios::ate)
        {
        case ios::in:
          return "r" + bin;
        case ios::out|ios::trunc:
        case ios::out:
          return "w" + bin;
        case ios::out|ios::app:
          return "a" + bin;
        case ios::in|ios::out:
          return "r+"+ bin;
        case ios::in|ios::out|ios::trunc:
          return "w+"+ bin;
        default:
          return "";
        }
      }
    
      void init()//Auslagerung aus den Konstruktoren (siehe 3.1)
      {
        setg(m_obuf+4,m_obuf+4,m_obuf+4);
      }
    public:
      cfbuf(FILE* dfile) : m_file(dfile)
      {
        init();
      }
      cfbuf(const string& dname, ios::openmode  mode)
      {
        m_file = fopen(dname.c_str(),getmode(mode).c_str());
        if(mode & ios::ate) seekoff(0,ios::end);
        init();
      }
      ~cfbuf()
      {
        fclose(m_file);
      }
    

    Mit weiteren Methoden ist es möglich, auch während des laufenden Betriebs eine neue Datei zu öffnen und wieder zu schließen:

    public:
      bool open(const string& dname, ios::openmode mode)
      {
        m_file = freopen(dname.c_str(),getmode(mode).c_str(),m_file);
        if(mode & ios::ate) seekoff(0,ios::end);
        init();
        return m_file!=NULL;
      }
    
      void close()
      {
        fclose(m_file);
        m_file = NULL;
      }
    
      bool isopen()
      { return m_file != NULL; }
    

    Anmerkung: Natürlich könnten auch die Streams selber die Dateiverwaltung übernehmen. Allerdings müsste dann der selbe Code in dreifacher Ausführung geschrieben werden, weil es drei Streamklassen gibt, die relativ unabhängig voneinander in die Stream-Heirarchie eingebunden werden sollten (als Kind von istream, ostream bzw. iostream).

    4.3 Steuerung der Pufferstrategie

    Ein Stream-Buffer bietet auch die Methode pubsetbuf(), mit der eventuell das Puffer-Verhalten beeinflusst werden kann. Hierbei hängt es vom verwendeten Buffer ab, ob und wie dieser auf den Aufruf reagiert.

    Für meinen Beispielpuffer bedeutet das, daß er z.B. den übergebenen Puffer zur internen Zwischenspeicherungg nutzen (anstelle der Zeichenarrays m_ibuf bzw. m_obuf), die Angaben mittels setvbuf() an die verwaltete C-Datei weitergeben oder auch die Anfrage komplett ignorieren könnte. Ich habe mich entschieden, die Anfrage zu ignorieren, deshalb zeige ich hier nur den prinzipiellen Aufbau dieser Funktion:

    virtual streambuf* setbuf(char* s,streamsize n)
    {
       //verarbeite den übergebenen Puffer
      return this;
    }
    

    5 Verbindung zum Stream

    Nachdem wir eine Streambuffer-Klasse haben, müssen wir C++ nur noch davon überzeugen, diese zu verwenden. Eigentlich reicht es dazu aus, ein geeignetes Objekt anzulegen und per rdbuf() an einen (i/o)stream zu übergeben. Allerdings ist es aus Sicht des Anwenders vorteilhafter, die notwendigen Verwaltungsaufgaben in eine eigene Streamklasse zu kapseln. Diese definiert ihren Streambuffer und kümmert sich darum, ihn mit Daten zu versorgen.

    class cfstream : public std::iostream
    {
    private:
      cfbuf buf;
    public:
      cfstream(const string& fname, openmode mode) : iostream(&buf), buf(fname,mode)
      {
      }
    private:
    };
    

    Eventuell könnte man die Streamklasse noch um Methoden erweitern, die im laufenden Betrieb Dateien schließen und wieder öffnen können. Diese rufen die entsprechenden Methoden des Puffers auf.

    Im obigen Beispiel habe ich nur einen bidirektionalen Stream (für Ein- und Ausgabe) erstellt. Für reine Ein- oder Ausgabestreams müsste man genauso vorgehen, nur mit dem Unterschied, daß diese von istream bzw. ostream abgeleitet werden müssen.

    6 Generische Version

    Im Allgemeinen Fall sollte ein Streambuffer nicht auf den Umgang mit char's beschränkt bleiben, sondern mit allen Zeichensätzen auskommen, mit denen die IOStream Bibliothek zu tun bekommt. Darum werden die Puffer gewöhnlich als Templates angelegt (siehe Kapitel 1). Und auch die übrigen Funktionen, die sich bisher an char's orientieren, müssen entsprechend angepasst werden:

    • Der zweite Parameter in allen fread()- und fwrite()-Aufrufen muß ersetzt werden durch 'sizeof(char_type)' (bei eigenen Puffern muß auf andere Weise die Größe des Zeichentyps an die I/O-Funktionen übergeben werden)
    • EOF muß ersetzt werden durch "traitT::eof()"
    • Rückgaben von Zeichen (return c;) müssen maskiert werden durch "traitT::not_eof(c)"
    • auch wenn es nicht unbedingt erforderlich ist, ist es eine gute Idee, sich an die Namensgebung der Standardbibliothek anzupassen.

    Mit diesen Anpassungen ergibt sich eine komplett einsatzfähige Klassenhierarchie für die eigene Datenquelle. Die Umsetzung meines Beispiels habe ich als Anhang an diesen Artikel angebracht.
    ~der Anhang kommt noch~



  • Sorry, wenn es hier eine Weile nicht weiterging, aber mein Schlepptop wollte in letzter Zeit nicht so, wie ich wollte. Könnte mal bitte jemand den folgenden Quelltext auf syntaktische (und logische) Fehler ansehen?

    #ifndef CFSTREAM_H
    #define CFSTREAM_H
    
    #include <iosfwd>
    #include <cstdio>
    #include <streambuf>
    
    using std::ios_base;//'using namespace std;' will ich lieber vermeiden
    
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_cfbuf : public std::basic_streambuf<chatT,traitT>
    {
    private:
      FILE* m_file;
    public:
    
    // Ausgabe (ungepuffert)
    protected:
      virtual int_type overflow (int_type c)
      {
        if(c!=EOF)
        {
          char z=c;
          if(fwrite(&z,sizeof(charT)1,1,m_file)!=1)
            return EOF;
        }
        return c;
      }
    
      virtual std::streamsize xsputn(const charT* s,std::streamsize num)
      {
        return static_cast<std::streamsize>(fwrite(s,sizeof(charT),num,m_file));
      }
    
    //Eingabe (gepufffert 6+4 Zeichen)
    private:
      enum {ibuf_size = 6, iback_size = 4};
      charT m_ibuf[iback_size+ibuf_size];
    public:
    
    protected:
      virtual int_type underflow()
      {
        if(gptr()<egptr())
          return *gptr();//noch Daten im Puffer vorhanden
    
        // Größe der Putback-Area ermitteln
        int nput = static_cast<int>(gptr() - eback());
        if(nput>iback_size) nput = iback_size;
        // und Putback-Area umkopieren
        memcpy(m_ibuf+(iback_size-nput),gptr()-nput,nput);
    
        // neue Zeichen lesen
        int num = fread(buffer+iback_size,sizeof(charT),ibuf_size,m_file);
        if(num<=0)
          return EOF;
    
        // setze Lesepuffer neu
        setg(m_ibuf+(iback_size-nput),m_ibuf+iback_size,m_ibuf+iback_size+num);
    
        return *gptr();
      }
    
    //Dateiverwaltung
    public:
      basic_cfbuf(const string& fname,ios_base::openmode mode)
      : m_file(fopen(fname.c_str(),getmode(mode).c_str())
      { 
        init();
        if(mode & ios_base::ate) seekoff(0,ios_base::end);
      }
      basic_cfbuf(FILE* dfile = NULL) : m_file(dfile)
      { init(); }
    
      ~basic_cfbuf()
      { close(); }
    
      bool open(const string& fname,ios_base::openmode mode)
      {
        if(m_file != NULL) fclose(m_file);
        m_file=fopen(fname.c_str(),getmode(mode).c_str());
        init();
        return m_file != NULL;
      }
      void close()
      { if(m_file != NULL) fclose(m_file); }
      bool isopen()
      { reurn m_file != NULL; }
    
    //Hilfsfunktionen
    private:
      //Initialisierung
      void init()
      {
        setg(m_ibuf+4,m_ibuf+4,m_ibuf+4);
      }
      //ios::openmode -> fopen Zugriffsmodus
      string getmode(ios_base::openmode m)
      {
        string bin = m & ios_base::binary ? "b" : "";
        switch(m & ~ios_base::binary & ~ios_base::ate)
        {
        case ios_base::in:
          return "r" + bin;
        case ios_base::out|ios_base::trunc:
        case ios_base::out:
          return "w" + bin;
        case ios_base::out|ios_base::app:
          return "a" + bin;
        case ios_base::in|ios_base::out:
          return "r+"+ bin;
        case ios_base::in|ios_base::out|ios_base::trunc:
          return "w+"+ bin;
        default:
          return "";
        }
      }
      //ios::seekdir -> fseek Basisposition
      int getbase(ios_base::seekdir b)
      {
        switch(b)
        {
        case ios_base::beg : return SEEK_SET;
        case ios_base::cur : return SEEK_CUR;
        case ios_base::end : return SEEK_END;
        }
      }
    };
    
    //Gemeinsame Basisklasse für alle CF-Streams
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_cf
    {
    protected:
      basic_cfbufs<charT,traitT> m_buf;
    public:
      basic_cf(const string& fname,std::ios_base::openmode fmode)
      : m_buf(fname,fmode) {}
      basic_cf(FILE* file = NULL)
      : m_buf(file) {}
    
      bool open(const string& fname,std::ios_base::openmode fmode)
      { return m_buf.open(fname,fmode); }
      bool isopen()
      { return m_buf.isopen(); }
      void close()
      { m_buf.close(); }
    };
    
    //Stream-Klassen:
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_cfstream : public basic_cf<charT,traitT>,
                           public basic_iostream<charT,traitT>
    {
    public:
      basic_cfstream(const string& fname,std::ios_base::openmode fmode)
      : basic_cf(fname,fmode),basic_iostream(&m_buf) {}
      basic_cfstream(FILE* file)
      : basic_cf(file),basic_iostream(&m_buf) {}
      basic_cfstream()
      : basic_cf(),basic_iostream(&m_buf) {}
    };
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_icfstream : public basic_cf<charT,traitT>,
                           public basic_istream<charT,traitT>
    {
    public:
      basic_icfstream(const string& fname,std::ios_base::openmode fmode)
      : basic_cf(fname,fmode),basic_istream(&m_buf) {}
      basic_icfstream(FILE* file)
      : basic_cf(file),basic_istream(&m_buf) {}
      basic_icfstream()
      : basic_cf(),basic_istream(&m_buf) {}
    };
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_ocfstream : public basic_cf<charT,traitT>,
                           public basic_ostream<charT,traitT>
    {
    public:
      basic_ocfstream(const string& fname,std::ios_base::openmode fmode)
      : basic_cf(fname,fmode),basic_ostream(&m_buf) {}
      basic_ocfstream(FILE* file)
      : basic_cf(file),basic_ostream(&m_buf) {}
      basic_ocfstream()
      : basic_cf(),basic_ostream(&m_buf) {}
    };
    
    typedef basic_cfstream<char>  cfstream;
    typedef basic_icfstream<char> icfstream;
    typedef basic_ocfstream<char> ocfstream;
    typedef basic_cfbuf<char>     cfbuf;
    
    typedef basic_cfstream<wchar_t>  wcfstream;
    typedef basic_icfstream<wchar_t> wicfstream;
    typedef basic_ocfstream<wchar_t> wocfstream;
    typedef basic_cfbuf<wchar_t>     wcfbuf;
    
    #endif//CFSTREAM_H
    

    (ich weiß, er ist etwas lang)



  • Hi,

    also bei Zeile 12 meckert der Compiler:

    d:\user files\visual studio 2005\projects\foobar\foobar\cfstream.hpp(14) : error C2886: 'std::ios_base' : symbol cannot be used in a member using-declaration
            C:\Programme\Microsoft Visual Studio 8\VC\include\xiosbase(183) : see declaration of 'std::ios_base'
    

    Hab das auf die schnelle durch ein using namespace std; im Header gefixed (ich weiß, böse).

    Ich bin noch nicht so ganz durchgestiegen.
    Folgende Änderungen:
    1. m_obuf hat gefehlt, ich hab's mal hinzugefügt
    2. Diverse static_casts eingebaut, damit der Compiler ruhe gibt
    3. Einige Rechtschreibfehler entfernt

    #ifndef CFSTREAM_HPP
    #define CFSTREAM_HPP
    
    #include <iosfwd>
    #include <cstdio>
    #include <streambuf>
    
    using namespace std;
    
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_cfbuf : public std::basic_streambuf<charT,traitT>
    {
    private:
      FILE* m_file;
    public:
    
    // Ausgabe (ungepuffert)
    protected:
      virtual int_type overflow (int_type c)
      {
        if(c!=EOF)
        {
          char z=c;
          if( fwrite(&z,sizeof(charT),1,m_file) != 1 )
            return EOF;
        }
        return c;
      }
    
      virtual std::streamsize xsputn(const charT* s,std::streamsize num)
      {
    	  return static_cast<std::streamsize>( fwrite(s,sizeof(charT),num,m_file) );
      }
    
    //Eingabe (gepufffert 6+4 Zeichen)
    private:
      enum {ibuf_size = 6, iback_size = 4};
      charT m_ibuf[iback_size+ibuf_size], m_obuf[iback_size+ibuf_size];
    public:
    
    protected:
      virtual int_type underflow()
      {
        if(gptr()<egptr())
          return *gptr();//noch Daten im Puffer vorhanden
    
        // Größe der Putback-Area ermitteln
        int nput = static_cast<int>( gptr() - eback() );
        if(nput>iback_size) nput = iback_size;
        // und Putback-Area umkopieren
        memcpy(m_ibuf+(iback_size-nput),gptr()-nput,nput);
    
        // neue Zeichen lesen
        int num = fread(m_ibuf+iback_size,sizeof(charT),ibuf_size,m_file);
        if(num<=0) 
    		return EOF;
    
        // setze Lesepuffer neu
        setg(m_obuf+(iback_size-nput),m_obuf+iback_size,m_obuf+iback_size+num);
    
        return *gptr();
      }
    
    //Dateiverwaltung
    public:
      basic_cfbuf(const string& fname,ios_base::openmode mode)
      : m_file(fopen(fname.c_str(),getmode(mode).c_str()))
      {
        init();
        if(mode & ios_base::ate) seekoff(0,ios_base::end);
      }
      basic_cfbuf(FILE* dfile = NULL) : m_file(dfile)
      { init(); }
    
      ~basic_cfbuf()
      { close(); }
    
      bool open(const string& fname,ios_base::openmode mode)
      {
        if(m_file != NULL) fclose(m_file);
        m_file=fopen(fname.c_str(),getmode(mode).c_str());
        init();
        return m_file != NULL;
      }
      void close()
      { if(m_file != NULL) fclose(m_file); }
      bool isopen()
      { reurn m_file != NULL; }
    
    //Hilfsfunktionen
    private:
      //Initialisierung
      void init()
      {
        setg(m_obuf+4,m_obuf+4,m_obuf+4);
      }
      //ios::openmode -> fopen Zugriffsmodus
      string getmode(ios_base::openmode m)
      {
        string bin = m & ios_base::binary ? "b" : "";
        switch(m & ~ios_base::binary & ~ios_base::ate)
        {
        case ios_base::in:
          return "r" + bin;
        case ios_base::out|ios_base::trunc:
        case ios_base::out:
          return "w" + bin;
        case ios_base::out|ios_base::app:
          return "a" + bin;
        case ios_base::in|ios_base::out:
          return "r+"+ bin;
        case ios_base::in|ios_base::out|ios_base::trunc:
          return "w+"+ bin;
        default:
          return "";
        }
      }
      //ios::seekdir -> fseek Basisposition
      int getbase(ios_base::seekdir b)
      {
        switch(b)
        {
        case ios_base::beg : return SEEK_SET;
        case ios_base::cur : return SEEK_CUR;
        case ios_base::end : return SEEK_END;
        }
      }
    };
    
    //Gemeinsame Basisklasse für alle CF-Streams
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_cf
    {
    protected:
      basic_cfbuf<charT,traitT> m_buf;
    public:
      basic_cf(const string& fname,std::ios_base::openmode fmode)
      : m_buf(fname,fmode) {}
      basic_cf(FILE* file = NULL)
      : m_buf(file) {}
    
      bool open(const string& fname,std::ios_base::openmode fmode)
      { return m_buf.open(fname,fmode); }
      bool isopen()
      { return m_buf.isopen(); }
      void close()
      { m_buf.close(); }
    };
    
    //Stream-Klassen:
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_cfstream : public basic_cf<charT,traitT>,
                           public basic_iostream<charT,traitT>
    {
    public:
      basic_cfstream(const string& fname,std::ios_base::openmode fmode)
      : basic_cf(fname,fmode),basic_iostream(&m_buf) {}
      basic_cfstream(FILE* file)
      : basic_cf(file),basic_iostream(&m_buf) {}
      basic_cfstream()
      : basic_cf(),basic_iostream(&m_buf) {}
    };
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_icfstream : public basic_cf<charT,traitT>,
                           public basic_istream<charT,traitT>
    {
    public:
      basic_icfstream(const string& fname,std::ios_base::openmode fmode)
      : basic_cf(fname,fmode),basic_istream(&m_buf) {}
      basic_icfstream(FILE* file)
      : basic_cf(file),basic_istream(&m_buf) {}
      basic_icfstream()
      : basic_cf(),basic_istream(&m_buf) {}
    };
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_ocfstream : public basic_cf<charT,traitT>,
                           public basic_ostream<charT,traitT>
    {
    public:
      basic_ocfstream(const string& fname,std::ios_base::openmode fmode)
      : basic_cf(fname,fmode),basic_ostream(&m_buf) {}
      basic_ocfstream(FILE* file)
      : basic_cf(file),basic_ostream(&m_buf) {}
      basic_ocfstream()
      : basic_cf(),basic_ostream(&m_buf) {}
    };
    
    typedef basic_cfstream<char>  cfstream;
    typedef basic_icfstream<char> icfstream;
    typedef basic_ocfstream<char> ocfstream;
    typedef basic_cfbuf<char>     cfbuf;
    
    typedef basic_cfstream<wchar_t>  wcfstream;
    typedef basic_icfstream<wchar_t> wicfstream;
    typedef basic_ocfstream<wchar_t> wocfstream;
    typedef basic_cfbuf<wchar_t>     wcfbuf;
    
    #endif //CFSTREAM_HPP
    

    Schreiben klappt, aber evtl. solltest du den Code doch noch mal selber kompilieren und durchchecken. Mir fehlt grad etwas das Verständnis bzw. die Zeit, mir alles zu erarbeiten.

    MfG

    GPC



  • GPC schrieb:

    also bei Zeile 12 meckert der Compiler:

    d:\user files\visual studio 2005\projects\foobar\foobar\cfstream.hpp(14) : error C2886: 'std::ios_base' : symbol cannot be used in a member using-declaration
            C:\Programme\Microsoft Visual Studio 8\VC\include\xiosbase(183) : see declaration of 'std::ios_base'
    

    Hab das auf die schnelle durch ein using namespace std; im Header gefixed (ich weiß, böse).

    Hmm, darüber müsste ich nochmal nachdenken - gibt sicher eine bessere Lösung als das 'using namespace std;' (und ich bin zu faul, jedes Mal std::ios_base zuschreiben).

    1. m_obuf hat gefehlt, ich hab's mal hinzugefügt

    Nein, das hat nicht gefehlt - m_obuf wird nur für eine gepufferte Ausgabe (siehe Kapitel 2.2) benötigt, nicht für die ungepufferte Ausgabe.

    2. Diverse static_casts eingebaut, damit der Compiler ruhe gibt

    Ich seh's mir mal an (Cast's gehören eigentlich auch zu dem, was ich vermeiden will - vermutlich ist es besser, mit size_t zu rechnen).

    3. Einige Rechtschreibfehler entfernt

    Thx.

    Schreiben klappt, aber evtl. solltest du den Code doch noch mal selber kompilieren und durchchecken. Mir fehlt grad etwas das Verständnis bzw. die Zeit, mir alles zu erarbeiten.

    Werde ich machen - aber dazu müsste ich meinen Schleppi wieder in Gang bekommen.



  • CStoll schrieb:

    1. m_obuf hat gefehlt, ich hab's mal hinzugefügt

    Nein, das hat nicht gefehlt - m_obuf wird nur für eine gepufferte Ausgabe (siehe Kapitel 2.2) benötigt, nicht für die ungepufferte Ausgabe.

    Okay, aber dann kompiliert der Code nicht, weil du in basic_cbuf::underflow() auf m_obuf zugreifst. Oder müsste das m_ibuf sein (der Kommentar sagt ja auch Lesepuffer)?

    Schreiben klappt, aber evtl. solltest du den Code doch noch mal selber kompilieren und durchchecken. Mir fehlt grad etwas das Verständnis bzw. die Zeit, mir alles zu erarbeiten.

    Werde ich machen - aber dazu müsste ich meinen Schleppi wieder in Gang bekommen.

    Viel Erfolg. 🙂 👍



  • GPC schrieb:

    CStoll schrieb:

    1. m_obuf hat gefehlt, ich hab's mal hinzugefügt

    Nein, das hat nicht gefehlt - m_obuf wird nur für eine gepufferte Ausgabe (siehe Kapitel 2.2) benötigt, nicht für die ungepufferte Ausgabe.

    Okay, aber dann kompiliert der Code nicht, weil du in basic_cbuf::underflow() auf m_obuf zugreifst. Oder müsste das m_ibuf sein (der Kommentar sagt ja auch Lesepuffer)?

    Ja, eigentlich sollte das m_ibuf sein (auch in der init()-Methode).

    Ich hab' deine Änderungen mal oben eingebaut.



  • Mir ist noch was aufgefallen:

    //Initialisierung
      void basic_cfbuf::init()
      {
        setg(m_ibuf+4,m_ibuf+4,m_ibuf+4);
      }
    

    Original war das m_obuf, ich hab's mal geändert. #include <string> hat auch gefehlt, hab 10 Minuten gebraucht, um's herauszufinden, scheiß Fehlermdeldung.
    Hab auch noch 'n pragma reingebaut, weil er ständig wegen fopen genölt hat.

    So, hier der neue Code:

    #ifndef CFSTREAM_HPP
    #define CFSTREAM_HPP
    
    #ifdef _MSC_VER
    #pragma warning(disable : 4996)
    #endif
    
    #include <iosfwd>
    #include <cstdio>
    #include <streambuf>
    #include <string>
    
    using namespace std;
    
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_cfbuf : public std::basic_streambuf<charT,traitT>
    {
    private:
      FILE* m_file;
    public:
    
    // Ausgabe (ungepuffert)
    protected:
      virtual int_type overflow (int_type c)
      {
        if(c!=EOF)
        {
          char z=c;
          if( fwrite(&z,sizeof(charT),1,m_file) != 1 )
            return EOF;
        }
        return c;
      }
    
      virtual std::streamsize xsputn(const charT* s,std::streamsize num)
      {
    	  return static_cast<std::streamsize>( fwrite(s,sizeof(charT),num,m_file) );
      }
    
    //Eingabe (gepufffert 6+4 Zeichen)
    private:
      enum {ibuf_size = 6, iback_size = 4};
      charT m_ibuf[iback_size+ibuf_size];
    public:
    
    protected:
      virtual int_type underflow()
      {
        if(gptr()<egptr())
          return *gptr();//noch Daten im Puffer vorhanden
    
        // Größe der Putback-Area ermitteln
        int nput = static_cast<int>( gptr() - eback() );
        if(nput>iback_size) nput = iback_size;
        // und Putback-Area umkopieren
        memcpy(m_ibuf+(iback_size-nput),gptr()-nput,nput);
    
        // neue Zeichen lesen
        int num = static_cast<int>( fread(m_ibuf+iback_size,sizeof(charT),ibuf_size,m_file) );
        if(num<=0) 
    		return EOF;
    
        // setze Lesepuffer neu
        setg(m_ibuf+(iback_size-nput),m_ibuf+iback_size,m_ibuf+iback_size+num);
    
        return *gptr();
      }
    
    //Dateiverwaltung
    public:
      basic_cfbuf(const string& fname,ios_base::openmode mode)
      : m_file(fopen(fname.c_str(),getmode(mode).c_str()))
      {
        init();
        if(mode & ios_base::ate) seekoff(0,ios_base::end);
      }
      basic_cfbuf(FILE* dfile = NULL) : m_file(dfile)
      { init(); }
    
      ~basic_cfbuf()
      { close(); }
    
      bool open(const string& fname,ios_base::openmode mode)
      {
        if(m_file != NULL) fclose(m_file);
        m_file=fopen(fname.c_str(),getmode(mode).c_str());
        init();
        return m_file != NULL;
      }
      void close()
      { if(m_file != NULL) fclose(m_file); }
      bool isopen()
      { reurn m_file != NULL; }
    
    //Hilfsfunktionen
    private:
      //Initialisierung
      void init()
      {
        setg(m_ibuf+4,m_ibuf+4,m_ibuf+4);
      }
      //ios::openmode -> fopen Zugriffsmodus
      string getmode(ios_base::openmode m)
      {
        string bin = m & ios_base::binary ? "b" : "";
        switch(m & ~ios_base::binary & ~ios_base::ate)
        {
        case ios_base::in:
          return "r" + bin;
    	case ios_base::out|ios_base::trunc:		// FALLTHROUGH
        case ios_base::out:
          return "w" + bin;
        case ios_base::out|ios_base::app:
          return "a" + bin;
        case ios_base::in|ios_base::out:
          return "r+"+ bin;
        case ios_base::in|ios_base::out|ios_base::trunc:
          return "w+"+ bin;
        default:
          return "";
        }
      }
      //ios::seekdir -> fseek Basisposition
      int getbase(ios_base::seekdir b)
      {
        switch(b)
        {
        case ios_base::beg : return SEEK_SET;
        case ios_base::cur : return SEEK_CUR;
        case ios_base::end : return SEEK_END;
        }
      }
    };
    
    //Gemeinsame Basisklasse für alle CF-Streams
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_cf
    {
    protected:
      basic_cfbuf<charT,traitT> m_buf;
    public:
      basic_cf(const string& fname,std::ios_base::openmode fmode)
      : m_buf(fname,fmode) {}
      basic_cf(FILE* file = NULL)
      : m_buf(file) {}
    
      bool open(const string& fname,std::ios_base::openmode fmode)
      { return m_buf.open(fname,fmode); }
      bool isopen()
      { return m_buf.isopen(); }
      void close()
      { m_buf.close(); }
    };
    
    //Stream-Klassen:
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_cfstream : public basic_cf<charT,traitT>,
                           public basic_iostream<charT,traitT>
    {
    public:
      basic_cfstream(const string& fname,std::ios_base::openmode fmode)
      : basic_cf(fname,fmode),basic_iostream(&m_buf) {}
      basic_cfstream(FILE* file)
      : basic_cf(file),basic_iostream(&m_buf) {}
      basic_cfstream()
      : basic_cf(),basic_iostream(&m_buf) {}
    };
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_icfstream : public basic_cf<charT,traitT>,
                           public basic_istream<charT,traitT>
    {
    public:
      basic_icfstream(const string& fname,std::ios_base::openmode fmode)
      : basic_cf(fname,fmode),basic_istream(&m_buf) {}
      basic_icfstream(FILE* file)
      : basic_cf(file),basic_istream(&m_buf) {}
      basic_icfstream()
      : basic_cf(),basic_istream(&m_buf) {}
    };
    template<typename charT,typename traitT=std::char_traits<charT> >
    class basic_ocfstream : public basic_cf<charT,traitT>,
                           public basic_ostream<charT,traitT>
    {
    public:
      basic_ocfstream(const string& fname,std::ios_base::openmode fmode)
      : basic_cf(fname,fmode),basic_ostream(&m_buf) {}
      basic_ocfstream(FILE* file)
      : basic_cf(file),basic_ostream(&m_buf) {}
      basic_ocfstream()
      : basic_cf(),basic_ostream(&m_buf) {}
    };
    
    typedef basic_cfstream<char>  cfstream;
    typedef basic_icfstream<char> icfstream;
    typedef basic_ocfstream<char> ocfstream;
    typedef basic_cfbuf<char>     cfbuf;
    
    typedef basic_cfstream<wchar_t>  wcfstream;
    typedef basic_icfstream<wchar_t> wicfstream;
    typedef basic_ocfstream<wchar_t> wocfstream;
    typedef basic_cfbuf<wchar_t>     wcfbuf;
    
    #endif //CFSTREAM_HPP
    

    Und mein Beispielcode, der im Übrigen funktioniert.

    #include <iostream>
    #include "cfstream.hpp"
    
    int main() {
    	{
    		ocfstream strm;
    		strm.open("foo.txt", ios::out); 
    		strm<<"Have a drink on me";
    		strm.close();
    	}
    
    	{
    		icfstream strm;
    		strm.open("foo.txt", ios::in);
    		std::string str;
    		strm>>str;
    		strm.close();
    		cout<<str<<endl;
    	}
        return EXIT_SUCCESS;
    }
    


  • Was mir beim überfliegen aufgefallen ist

    1. wenn cstdio und fwrite, dann bitte std::fwrite etc. 🙂
    2. anstelle EOF lieber traitT::eof() (dafür sind die char_traits ja da :))
    3. bitte kein int erzwingen, wo kein int benötigt wird!

    int num = static_cast<int>( fread(m_ibuf+iback_size,sizeof(charT),ibuf_size,m_file) );
        if(num<=0)
            return EOF;
    

    äh, es hat schon einen Sinn, das std::fread ein size_t zurück gibt 🙂

    The functions fread() and fwrite() advance the file position indicator
    for the stream by the number of bytes read or written. They return the
    number of objects read or written. If an error occurs, or the end-of-
    file is reached, the return value is a short object count (or zero).

    The function fread() does not distinguish between end-of-file and error,
    and callers must use feof(3) and ferror(3) to determine which occurred.
    The function fwrite() returns a value less than nmemb only if a write
    error has occurred.

    4. bitte const-correctness beachten!
    5.

    bool isopen()
      { return m_file != NULL; }
    

    das sieht ein bisschen gedoppelt aus 😉
    6. Ein Hinweis oder gar der Einbau von Boost.Iostream wäre toll 🙂
    7. Ein Hinweis darauf, das man für Dateien eigentlich von std::basic_filebuf ableitet wäre praktisch (ich nehme mal an stream_buf nimmst du hier, um es allgemein zu zeigen)



  • rüdiger schrieb:

    1. wenn cstdio und fwrite, dann bitte std::fwrite etc. 🙂

    Werde ich machen

    2. anstelle EOF lieber traitT::eof() (dafür sind die char_traits ja da :))

    *grübelt* Im Artikel habe ich es schon erwähnt, aber bei der Umsetzung muß es untergegangen sein.

    3. bitte kein int erzwingen, wo kein int benötigt wird!

    int num = static_cast<int>( fread(m_ibuf+iback_size,sizeof(charT),ibuf_size,m_file) );
        if(num<=0)
            return EOF;
    

    äh, es hat schon einen Sinn, das std::fread ein size_t zurück gibt 🙂

    OK, wird auch geändert.

    4. bitte const-correctness beachten!

    Auf welche Stelle(n) beziehst du dich damit?

    bool isopen()
      { return m_file != NULL; }
    

    das sieht ein bisschen gedoppelt aus 😉

    Inwiefern? Klar könnte ein 'return m_file;' ausreichen, aber der explizite Vergleich ist optisch deutlicher 😉

    6. Ein Hinweis oder gar der Einbau von Boost.Iostream wäre toll 🙂

    Ich werde darauf hinweisen. Um es einzubauen, müsste ich mich etwas intensiver mit den Boost-Streams beschäftigen.

    7. Ein Hinweis darauf, das man für Dateien eigentlich von std::basic_filebuf ableitet wäre praktisch (ich nehme mal an stream_buf nimmst du hier, um es allgemein zu zeigen)

    Es ging mir nicht um Dateien (im Vorwort habe ich doch gesagt, daß dafür fstream und Kollegen schon vorhanden sind), sondern um den prinzipiellen Aufbau.



  • CStoll schrieb:

    4. bitte const-correctness beachten!

    Auf welche Stelle(n) beziehst du dich damit?

    zB auf das isopen. Zwar macht das keine Probleme in dem Kontext, aber einfach der Vollständigkeit halber

    7. Ein Hinweis darauf, das man für Dateien eigentlich von std::basic_filebuf ableitet wäre praktisch (ich nehme mal an stream_buf nimmst du hier, um es allgemein zu zeigen)

    Es ging mir nicht um Dateien (im Vorwort habe ich doch gesagt, daß dafür fstream und Kollegen schon vorhanden sind), sondern um den prinzipiellen Aufbau.

    Ja, das verstehe ich auch. Nur wenn jetzt jemand kommt und einen Streambuf für eine eigene Unterart von Dateien implementieren will, solltest du ihn darauf hinweisen, dass man das eigentlich mit std::basic_filebuf macht.


Anmelden zum Antworten