string ist bei Ausgabe mit printf (null)



  • Nexus schrieb:

    audacia schrieb:

    (In der Praxis benutzt man std::fstream natürlich nach Möglichkeit ohnehin nicht.)

    Wie?

    Nun, die Streams sind in allen mir bekannten Implementationen langsamer als C-Streams (teilweise um Größenordnungen), und außerdem sind sie nicht moduslos, was Fehler in der Anwendung begünstigt und den korrekten Umgang massiv erschwert. Gewöhnlich ist man mit einem kleinen RAII-Wrapper um die C-Funktionen viel besser dran.



  • Okay, das kann gut sein, ich kenne die C-Funktionen aber auch zu wenig, um das genau beurteilen zu können.

    Naja, ich hatte bisher eigentlich nicht viele Probleme mit std::fstream , für meine Anwendungen reicht es. Ich finde zum Beispiel auch die Operatoren >> und << sehr komfortabel und habe momentan nicht wirklich Lust, alle Überladungen nachzuprogrammieren. 😉



  • Nexus schrieb:

    Okay, das kann gut sein, ich kenne die C-Funktionen aber auch zu wenig, um das genau beurteilen zu können.

    Man braucht die C-Funktionen nicht zu kennen, um zu sehen, daß es sich bei fstream um einen gewaltigen Designfehler handelt. Beispiele: die Existenz von fstream::good() und fstream::close().



  • audacia schrieb:

    Man braucht die C-Funktionen nicht zu kennen, um zu sehen, daß es sich bei fstream um einen gewaltigen Designfehler handelt. Beispiele: die Existenz von fstream::good() und fstream::close().

    Ja, die Streams sind zum Teil etwas speziell. Allerdings habe ich die bisher nicht so kritisch betrachtet, was auch daran liegen könnte, dass ich kaum Alternativen kenne.

    Aber weshalb erachtest du die beiden Methoden als Designfehler? Weil sie durch andere Memberfunktionen eigentlich bereits abgedeckt sind (z.B. Dtor bei close() )?


  • Administrator

    audacia schrieb:

    Nun, die Streams sind in allen mir bekannten Implementationen langsamer als C-Streams (teilweise um Größenordnungen), und außerdem sind sie nicht moduslos, was Fehler in der Anwendung begünstigt und den korrekten Umgang massiv erschwert.

    1. Geschwindigkeit:
    http://www.cplusplus.com/reference/iostream/streambuf/pubsetbuf/
    Viele Implementationen haben standardmässig keinen Puffer gesetzt, weshalb sie als deutlich langsamer erscheinen. Der Puffer kann verdammt viel ausmachen.

    2. Moduslos:
    http://www.cplusplus.com/reference/iostream/ios/exceptions/
    Man kann sich über Modusänderungen sehr schnell und explizit informieren lassen.

    Grüssli



  • Nexus schrieb:

    Aber weshalb erachtest du die beiden Methoden als Designfehler?

    fstream::good(), weil es impliziert, daß ich stets manuell auf Fehler überprüfen muß. Wozu gibt es denn Exceptions?

    fstream::close(), weil es das Objekt in einen Modus versetzt, in dem der Aufruf mancher Methoden nicht mehr gültig ist. Zudem steckt fstream noch in einer Vererbungshierarchie, in der es auf diese Weise das LSP verletzt.

    Dravere schrieb:

    2. Moduslos:
    http://www.cplusplus.com/reference/iostream/ios/exceptions/
    Man kann sich über Modusänderungen sehr schnell und explizit informieren lassen.

    Das ist nicht, was ich mit "moduslos" meine; siehe meine Ausführungen zu fstream::close().

    Und daß man das Verhalten im Fehlerfalle umstellen kann, macht die Sache IMHO nur noch schlimmer, weil man dadurch ein fstream-Objekt mit noch einem Zustand behaftet, der ihn eigentlich nichts angeht. Beispiel:

    void tryRead (istream& stream, ...)
    {
        stream.read (...);
        if (!stream.good ())
            ...; // set default value
    }
    void foo (void)
    {
        std::fstream theFile ("someFile", ...);
        theFile.exceptions (>alles<);
        ...
        tryRead (stream, ...); // <--
    }
    

    Hier verursacht der Aufruf von tryRead() unerwartetes Verhalten, da tryRead() sich auf das Standardverhalten des Streams verläßt. Korrekt müßte man tryRead() also so implementieren:

    class StreamExceptionStatePreserver
    {
    private:
        ios_base::iostate prevState;
    public:
        StreamExceptionStatePreserver (ios& theStream, ios_base::iostate newState)
         : prevState (theStream.exceptions ())
        { theStream.exceptions (newState); }
        ~StreamExceptionStatePreserver (void)
        { theStream.exceptions (prevState); }
    };    
    
    void tryRead (istream& stream, ...)
    {
        StreamExceptionStatePreserver esp (stream, >garnix<);
        stream.read (...);
        if (!stream.good ())
            ...; // set default value
    }
    

    Ich weiß nicht, wie es dir geht, aber spätestens da schreit sämtliche mir verbliebene Intuition laut "Designfehler".

    Eine Klasse, die derartige Workarounds erfordert, wird von mir i.d.R. ohnehin erst von einem adäquaten Wrapper umgeben, der die Komplexität in der Anwendung reduziert. Und wenn wir ohnehin beim Wrappen sind, können wir auch gleich die C-Funktionen wrappen, weil sie viel weniger Ballast haben und in der Praxis schlicht schneller sind, Puffer hin oder her.


  • Administrator

    @audacia,
    1. Wieso verletzt fstream das Liskovsches Substitutionsprinzip? Leuchtet mir gerade nicht ein.
    2. Bei den Exceptions setze ich immer den richtigen Status für das Programm oder Bibliothek gleich beim Anfang der Arbeit. Also wenn ein Stream übergeben wird, dann hole ich mir das streambuf Objekt raus und übergebe es einem eigenen Streamobjekt.
    3. Das mit der Geschwindigkeit verstehe ich nicht. Ich habe schon selber Tests durchgeführt und zwar mit einer XML Bibliothek. Allerdings muss ich sagen, dass meine XML Files sich in Grössenordnungen von 1 - 2 MiB bewegt haben. Ich konnte keinen Unterschied messen. Was für mich bedeutet, dass es wahrscheinlich erst bei wirklich grossen Files unter Umständen zu einer Differenz kommt. Und wahrscheinlich ist die Differenz eher klein. In den meisten Fällen ist die "Anklage" allerdings völlig übertrieben.
    4. Wenn man wirklich etwas an den Streams ankreiden möchte, dann sollte man dies beim ganzen Locale und Facet Zeug machen. Dort ist meiner Meinung nach wirklich ein Designfehler passiert. Das Zeug ist viel zu komplex, es verstösst gegen Speicherzugehörigkeit (wer Speicher holt, gibt ihn frei), ist unübersichtlich und unverständlich, viel zu umfangreich für kleine Dinge, usw. usf.

    5. Gibt es eigentlich Vorschläge für Verbesserung der Streams? Bzw. gibt es einfachere/bessere Streambibliotheken? Wäre mal interessant sich mit dem Zeug auseinander zu setzen.

    Grüssli



  • Dravere schrieb:

    1. Wieso verletzt fstream das Liskovsches Substitutionsprinzip? Leuchtet mir gerade nicht ein.

    Nun, std::fstream definiert einen Zustand, in dem der korrespondierende Stream überhaupt nicht existiert und deshalb der Aufruf aller auf dem Stream operierender Methoden fehlschlägt. Die Basisklasse std::iostream tut das nicht - für sie gibt es nur Streams in intaktem und nicht intaktem Zustand.

    Dravere schrieb:

    2. Bei den Exceptions setze ich immer den richtigen Status für das Programm oder Bibliothek gleich beim Anfang der Arbeit. Also wenn ein Stream übergeben wird, dann hole ich mir das streambuf Objekt raus und übergebe es einem eigenen Streamobjekt.

    Es ist schön, daß du im eigenen Code vorsorgst, aber das hat nun aber doch nichts damit zu tun, daß das Design der Streams in diesem Punkt fragwürdig ist, zu tun, oder?

    Dravere schrieb:

    4. Wenn man wirklich etwas an den Streams ankreiden möchte, dann sollte man dies beim ganzen Locale und Facet Zeug machen. Dort ist meiner Meinung nach wirklich ein Designfehler passiert. Das Zeug ist viel zu komplex, es verstösst gegen Speicherzugehörigkeit (wer Speicher holt, gibt ihn frei), ist unübersichtlich und unverständlich, viel zu umfangreich für kleine Dinge, usw. usf.

    Ja, da stimme ich uneingeschränkt zu. Aber falls du damit sagen willst, daß das Design der Streamklassen ansonsten in Ordnung ist, dann wäre es an der Zeit, daß du dich mal zu meinen Argumenten äußerst.


  • Administrator

    audacia schrieb:

    Nun, std::fstream definiert einen Zustand, in dem der korrespondierende Stream überhaupt nicht existiert und deshalb der Aufruf aller auf dem Stream operierender Methoden fehlschlägt. Die Basisklasse std::iostream tut das nicht - für sie gibt es nur Streams in intaktem und nicht intaktem Zustand.

    Das ist aber kein Verstoss gegen das Liskovsche Substitutionsprinzip. LSP geht genau in die andere Richtung. Es müssen alle beweisbaren Eigenschaft der Basisklasse auch für die abgeleitete Klasse gelten. Das ist auch der Fall.

    std::fstream erweitert nur die Eigenschaften und das ist auch der Sinn von Vererbung!

    audacia schrieb:

    Es ist schön, daß du im eigenen Code vorsorgst, aber das hat nun aber doch nichts damit zu tun, daß das Design der Streams in diesem Punkt fragwürdig ist, zu tun, oder?

    Fragwürdig ist aber nicht gleich ein Designfehler. Ich finde es gar nicht so fragwürdig, für mich ergibt das zum Teil durchaus einen Sinn. Man hat verschiedene Möglichkeiten. Und die exceptionfreie Möglichkeit hat durchaus seine Vorteile. Ich verwende oft beide Möglichkeiten.

    Man könnte vielleicht die Frage in den Raum stellen, wieso man nicht zwei Klassen gemacht hat. Eine mit Exception und eine ohne Exception. Das hätte vielleicht eine bessere Abgrenzung zwischen beiden Versionen geschaffen. Es hätte aber womöglich auch eine Menge an Codeduplizierung geschaffen.

    Ich habe nichts dagegen, wenn man die Sache überdenkt. Allerdings sollte man zwei Dinge im Hinterkopf behalten:
    1. Es können immer Fehler beim Design passieren. Das heisst aber nicht, dass die Klassen dann gleich nutzlos sind.
    2. Nachher ist man immer klüger 😉

    Dravere schrieb:

    Aber falls du damit sagen willst, daß das Design der Streamklassen ansonsten in Ordnung ist, dann wäre es an der Zeit, daß du dich mal zu meinen Argumenten äußerst.

    Nein, ich halte die Streamklassen ansonsten nicht einfach für in Ordnung. Aber ich halte sie auch nicht für einen völligen Designfehler und finde auch nicht, dass man sie gleich wegwerfen soll. Alles ist immer verbesserungswürdig. Ich finde die Ideen der Streamklassen gar nicht so schlecht, die Umsetzung lässt höchstens zum Teil zu wünschen übrig. Aber es ist definitiv nicht so, dass man sie nicht verwenden könnte.

    Ich persönlich habe ein wenig das Gefühl, dass du aus einer Mücke einen Elefanten machst und dich dann darüber beschwerst, dass du den Elefanten nicht hochheben kannst 😃

    Grüssli



  • Dravere schrieb:

    Das ist aber kein Verstoss gegen das Liskovsche Substitutionsprinzip. LSP geht genau in die andere Richtung. Es müssen alle beweisbaren Eigenschaft der Basisklasse auch für die abgeleitete Klasse gelten. Das ist auch der Fall.

    Das ist, technisch gesehen, deshalb kein Verstoß, weil die Basisklasse bereits die Möglichkeit, aus Gründen mangelnder Streamintegrität eine IO-Operation fehlschlagen zu lassen, einräumt. Jedoch: ist die Nichtexistenz eines Streams infolge einer Benutzeraktion dasselbe wie mangelnde Streamintegrität? Anders gesagt, du kannst das Objekt in einen Zustand versetzen, der im Interface der Basisklasse gar nicht vorgesehen ist. Um dieses Designproblem - die logische Verletzung des LSP - zu umgehen, wurde eben festgesetzt, daß nach dem Schließen eines Streams alle weiteren Operationen das bad-Flag setzen, zu dem die Dokumentation schreibt:
    [

    C++.com schrieb:

    This flag is set by standard input operations when an error that caused the loss of integrity of the stream happened.

    ](http://www.cplusplus.com/reference/iostream/ios/bad/)
    fstream führt nun eben eine Funktion ein, um diesen Zustand explizit herbeizuführen, und das aus einem Grund, der damit eigentlich gar nichts zu tun hat. Besser gewesen wäre, ein neues Flag wie "available" einzuführen, das diesen speziellen Zustand signalisiert - wofür man natürlich unschönerweise das Interface hätte verändern müssen. Aber noch viel besser wäre es gewesen, wenn man diese furchtbare Idee mit den Fehlerzuständen gar nicht erst umgesetzt hätte.

    Dravere schrieb:

    2. Nachher ist man immer klüger 😉

    Damit hast du, denke ich, das meiste, was zu den C++-Streams zu sagen wäre, auf den Punkt gebracht.

    Dravere schrieb:

    Ich persönlich habe ein wenig das Gefühl, dass du aus einer Mücke einen Elefanten machst und dich dann darüber beschwerst, dass du den Elefanten nicht hochheben kannst 😃

    Ich wünschte, es wäre so. Für den Vergleich mit einer Mücke sind die C++-Streams leider viel zu prävalent 😞


Anmelden zum Antworten