Fragen zu sentry, formatierte/unformatierte Eingabe und operator>>



  • Hallo alle!

    In diesem Thread geht es um das Einlesen von Windows-BMP-Dateien.

    Mir geht es jetzt um das Einlesen von integralen Datentypen und das Einlesen von structs mit hilfe von operator>>():

    Beim stöbern auf cppreference.com bin ich auf std::basic_istream::sentry gestoßen.

    Dort steht:

    An object of class basic_istream::sentry is constructed in local scope at the beginning of each member function of std::basic_istream that performs input (both formatted and unformatted). Its constructor prepares the input stream: checks if the stream is already in a failed state, flushes the tie()'d output streams, skips leading whitespace unless noskipws flag is set, and performs other implementation-defined tasks if necessary. All cleanup, if necessary, is performed in the destructor, so that it is guaranteed to happen if exceptions are thrown during input.

    Ich selbst hab den operator>> z.B. so implementiert

    class klasse {
      std::istream &is;
    
    [...]
      // Sebi hat da was hübscheres ala:
      // template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
      // Aber ich bin noch nicht so fit mit Templates, daher die einfache Variante (obwohl es verständlich ist):
      template <typename T>
      klasse & operator>>(T & t)
      {
        if(is.good())
          is.read( reinterpret_cast<char*>(&t), sizeof(T) );
        return is;
      }
    };
    

    Oder ohne Abfrage weil ich mir dachte read prüft sowieso den Status und springt direkt zurück wenn is.good() == false ist.
    Was soll man auch machen, das failbit zusätzlich setzen?

    [...]
      template <typename T>
      klasse & operator>>(T & t)
      {
        is.read( reinterpret_cast<char*>(&t), sizeof(T) );
        return is;
      }
    [...]
    

    Tja, und jetzt frage ich mich ob das der Weg ist den man gehen soll:

    [...]
      template <typename T>
      klasse & operator>>(T & t)
      {
        std::istream::sentry sentry(is, true);
        if (sentry)
          is.read( reinterpret_cast<char*>(&t), sizeof(T) );
        return is;
      }
    [...]
    

    Nur, ist das nötig. Oder gibt es bestimmte Situationen wo das besser ist?
    Und, wenn es besser ist und ich mehrere read-Aufrufe habe sollte ich das vor jeden read testen (meine Meinung) oder alle reads in einen if-Block?

    [...]
        std::istream::sentry sentry(is, true);
        if (sentry)
          is.read( reinterpret_cast<char*>(&var1), sizeof(var1) );
        if (sentry)
          is.read( reinterpret_cast<char*>(&var2), sizeof(var2) );
    [... oder ...]
    if (sentry) {
      is.read( reinterpret_cast<char*>(&var1), sizeof(var1) );
      is.read( reinterpret_cast<char*>(&var2), sizeof(var2) );
      }
    

    In dem Text auf cppreference.com steht das jede Lesefunktion von std::istream
    sentry benutzt, egal ob formatierte oder unformatierte Eingabe.
    In dem Infokästchen zum Konstruktor steht wiederum „Prepares the stream for formatted input.“

    Es ist unten auf der Seite auch ein Beispiel für einen operator>>:

    std::istream& operator>>(std::istream& is, Foo& f)
    {
        std::istream::sentry s(is);
        if (s)
            is.read(f.n, 5);
        return is;
    }
    

    Was meint ihr dazu?

    So, jetzt geht's weiter mit den Fragen:
    Ich glaube ich habe möglicherweise den Unterschied zw. FormattedInputFunction und UnformattedInputFunction noch nicht richtig verstanden.

    Unter formatierter Eingabe habe ich immer das konvertieren von lesbaren Text nach z.B. int verstanden, inkl. der Datums-/Zahlenumwandlung nach der eingestellten locale.
    Typische Eingaberoutinen sind dann operator>>() aber auch get(). Bei unformatierter das typische read() das einfach eine Anzahl char/wchar_t ohne Umwandlung einliest.

    Und dann kamen mir ein paar Fragen zum Stil auf: Sollte man überhaupt den operator>> für das binäre Einlesen benutzen?
    Also es stellt sich mir die Frage ob erstens das binäre Einlesen in integrale Datentypen (== unformatiert) vom Konzept
    her falsch ist und auf der anderen Seite man das einlesen einer ganzen struct wiederum als formatierte Eingabe sehen kann (== vom Konzept richtig)?
    Also quasi eine binäre Eingabe „formatiert“ in eine struct schreiben.

    Und noch eine Frage zum Einlesen von Daten in eine struct:
    Wäre der richtige Weg zum Einlesen so wie es die stdlib mit std::get_time() macht?

    Was ist get_time eigentlich? eine Funktion oder eine Klasse?

    template <class charT> /*T9*/ get_time(struct tm* tmb, const charT* fmt);
    

    Sie müsste doch eine Refernz auf... zurückgeben... Schere im Kopf, moment:

    stream >> get_time(&t, fmt)  ==  stream.operator>>( get_time(&t, fmt) )
    

    Ok, get_time ist hier eine (Template) inline-Funktion und gibt eine struct _get_time zurück, genauer ein Template (g++-5)
    Also gibt es einen operator>>(_get_time & gt). Wäre das zumindest geklärt. Oder Denkfehler meinerseits?

    Eine kleine Frage noch:
    Gibt es einen Grund warum z.B. operator>>(int) nicht auf binäres lesen (also sizeof(int)) umschaltet wenn man einen istream mit std::ios_base::openmode::binary öffnet?

    Vielleicht hat jemand ein paar Antworten,
    einen schönen Tag noch



  • Jeder Aufruf von .read() erstellt bereits ein sentry Objekt wie hier beschrieben. Wenn man also nicht noch zusätzlichen Whitespace überspringen will, wie in dem Beispiel, dann braucht man es selbst nicht.

    Ob es sinnvoll ist den operator>> für binäre Sachen zu missbrauchen kann man sich streiten. Ich finde es in diesem Fall einfach praktischer weil man alles hintereinander weg schreiben kann.

    std::get_time ist eine Funktion und gibt einen nicht näher festgelegten Typ zurück.



  • sebi707 schrieb:

    Jeder Aufruf von .read() erstellt bereits ein sentry Objekt wie hier beschrieben. Wenn man also nicht noch zusätzlichen Whitespace überspringen will, wie in dem Beispiel, dann braucht man es selbst nicht.

    Ob es sinnvoll ist den operator>> für binäre Sachen zu missbrauchen kann man sich streiten. Ich finde es in diesem Fall einfach praktischer weil man alles hintereinander weg schreiben kann.

    std::get_time ist eine Funktion und gibt einen nicht näher festgelegten Typ zurück.

    Zu 1) Dann habe ich da was missverstanden und kann mir das sparen 🙂

    Zu 2) Ja, sieht schöner aus und scheinen ja auch alle so zu machen...

    Zu get_time():

    Hmm, wenn ich das richtig sehe geht es bei get_time() nur darum 2 Argumente auf der rechten Seite des operator>> angeben zu können...

    Hier sieht das so aus:
    (gcc version 5.3.1 20151207 [gcc-5-branch revision 231355] (SUSE Linux))

    template<typename _CharT>
        struct _Get_time
        {
          std::tm*      _M_tmb;
          const _CharT* _M_fmt;
        };
    
      template<typename _CharT>
        inline _Get_time<_CharT>
        get_time(std::tm* __tmb, const _CharT* __fmt)
        { return { __tmb, __fmt }; }
    
      template<typename _CharT, typename _Traits>
        basic_istream<_CharT, _Traits>&
        operator>>(basic_istream<_CharT, _Traits>& __is, _Get_time<_CharT> __f)
    {...}
    

    Ich tippe

    stream >> get_time(&tm, "format");
    

    ein, folgendes passiert:

    Der operator>>(_Get_time) wird aufgerufen.

    Dieser ruft get_time() mit einer *Adresse* auf eine std::tm struct und einem Formatstring (by_value) auf die ich get_time() übergeben habe.

    get_time() wiederum gibt *by_value* die aus 2 Werten bestehende interne struct _Get_time an operator>> zurück.

    std::get_time() wird mit den Adressen einer std::tm-struct und einem Formatstring als Argumente aufgerufen. Diese macht nichts und gibt die 2 Argumente 1:1 by_value als interne struct _Get_time zurück.

    Mit dem Rückgabewert vom Typ _Get_time wird der operator>>(basic_istream&, _Get_time) aufgerufen.

    Aus der by_value übergebenen struct _Get_time holt sich der operator>> die Adresse des std::tm-Objekts heraus und füllt diese....

    Hoffe ich habe das so richtig interpretiert.

    Edit: siehe unten



  • Deine Interpretation stimmt soweit, außer das operator>> natürlich erst aufgerufen wird nachdem get_time() durch ist. Und das ist natürlich nur eine Möglichkeit das zu implementieren. Der Standard schreibt nicht vor wie man es machen soll, nur was passieren soll. In anderen Implementierung kann das struct ganz anders heißen, andere Member haben oder überhaupt gar kein struct sein.



  • sebi707 schrieb:

    Deine Interpretation stimmt soweit, außer das operator>> natürlich erst aufgerufen wird nachdem get_time() durch ist.

    Ja natürlich! Jetzt hab ich das extra so genau eingetippt und doch noch ein Fehler drin.

    sebi707 schrieb:

    Und das ist natürlich nur eine Möglichkeit das zu implementieren. Der Standard schreibt nicht vor wie man es machen soll, nur was passieren soll. In anderen Implementierung kann das struct ganz anders heißen, andere Member haben oder überhaupt gar kein struct sein.

    Jep, ich wollte mir halt angucken wie es praktisch gemacht wird. Hatte einen Knoten im Kopf von wg. rechter Operand ist eine Funktion, was gibt die Funktion zurück und welcher operator>> wird dann aufgerufen... Passiert mir öfters, dann muss ich mir das selbst so aufschreiben...



  • Eine Frage noch @sebi707:

    Dein Beispielcode für die BinaryReader-Klasse aus dem anderen Thread:

    template <class T, class = std::enable_if_t<std::is_integral<T>::value>> 
      BinaryReader& operator>> (T& value) 
      { 
        m_in.read(reinterpret_cast<char*>(&value), sizeof(T)); 
        return *this; 
      }
    

    Ich hatte zuerst Fehler bekommen weil ich #include <type_traits> vergessen hatte. Als ich 'enable' in die Suche von cppreference.com eingetippt habe bekam ich als Ergebnis (unter anderen)
    std::enable_if serviert. std::enable_if_t geht aber auch.

    Ist das beides das selbe, std::enable_if_t wird nicht bei cppreference.com gelistet.
    Ich vermute das es früher so hieß und dann umbenannt wurde, den alten Bezeichner hat man dann aus Kompatibilitätsgründen beibehalten?

    g++ nimmt beide und meckert auch nicht wg. depreated oder so...

    Bin erstmal raus für eine Stunde...



  • dirkski schrieb:

    Ist das beides das selbe, std::enable_if_t wird nicht bei cppreference.com gelistet.
    Ich vermute das es früher so hieß und dann umbenannt wurde, den alten Bezeichner hat man dann aus Kompatibilitätsgründen beibehalten?

    Nein es ist nicht beides das Gleiche. Die Schreibweise std::enable_if_t<zeug> ist die Kurzform (seit C++14) von typename std::enable_if<zeug>::type . Wenn du einfach das _t weglässt kriegst du nicht das was man erwartet.



  • sebi707 schrieb:

    dirkski schrieb:

    Ist das beides das selbe, std::enable_if_t wird nicht bei cppreference.com gelistet.
    Ich vermute das es früher so hieß und dann umbenannt wurde, den alten Bezeichner hat man dann aus Kompatibilitätsgründen beibehalten?

    Nein es ist nicht beides das Gleiche. Die Schreibweise std::enable_if_t<zeug> ist die Kurzform (seit C++14) von typename std::enable_if<zeug>::type . Wenn du einfach das _t weglässt kriegst du nicht das was man erwartet.

    Ok ok... Ich hätte mir mal das Example auf cppreference.com angucken können, dann hätte ich es gesehen...

    Danke nochmal 🙂


Anmelden zum Antworten