Wer hat die IOStreams von C ++ entworfen / entworfen und würde sie nach heutigen Maßstäben immer noch als gut gestaltet betrachten? [geschlossen]

127

Zunächst mag es so aussehen, als würde ich nach subjektiven Meinungen fragen, aber das ist nicht das, wonach ich strebe. Ich würde gerne einige fundierte Argumente zu diesem Thema hören.


In der Hoffnung, einen Einblick in das Design eines modernen Streams / Serialisierungs-Frameworks zu bekommen, habe ich mir kürzlich eine Kopie des Buches Standard C ++ IOStreams and Locales von Angelika Langer und Klaus Kreft besorgt . Ich dachte mir, wenn IOStreams nicht gut gestaltet wäre, hätte es es überhaupt nicht in die C ++ - Standardbibliothek geschafft.

Nachdem ich verschiedene Teile dieses Buches gelesen habe, habe ich Zweifel, ob IOStreams aus architektonischer Sicht mit der STL vergleichbar ist. Lesen Sie beispielsweise dieses Interview mit Alexander Stepanov (dem "Erfinder" der STL) , um mehr über einige Designentscheidungen zu erfahren, die in die STL eingeflossen sind.

Was mich besonders überrascht :

  • Es scheint unbekannt zu sein, wer für das Gesamtdesign von IOStreams verantwortlich war (ich würde gerne Hintergrundinformationen dazu lesen - kennt jemand gute Ressourcen?);

  • Sobald Sie unter die unmittelbare Oberfläche von IOStreams eintauchen, z. B. wenn Sie IOStreams mit Ihren eigenen Klassen erweitern möchten, gelangen Sie zu einer Schnittstelle mit ziemlich kryptischen und verwirrenden Mitgliedsfunktionsnamen, z. B. getloc/ imbue, uflow/ underflow, snextc/ sbumpc/ sgetc/ sgetn, pbase/ pptr/ epptr(und es gibt wahrscheinlich noch schlimmere Beispiele). Dies macht es so viel schwieriger, das Gesamtdesign und die Zusammenarbeit der einzelnen Teile zu verstehen. Sogar das Buch, das ich oben erwähnt habe, hilft nicht so viel (IMHO).


Also meine Frage:

Wenn Sie Richter von den heutigen Software - Engineering - Standards haben (wenn es tatsächlich ist eine allgemeine Einigung über diese), würde C ++ 's iostreams noch berücksichtigt wird gut gestaltet? (Ich möchte meine Fähigkeiten im Bereich Software-Design nicht durch etwas verbessern, das allgemein als veraltet gilt.)

stakx - nicht mehr beitragen
quelle
7
Interessante Meinung von Herb Sutter stackoverflow.com/questions/2485963/… :) Schade, dass dieser Typ SO nach nur wenigen Tagen der Teilnahme verlassen hat
Johannes Schaub - litb
5
Gibt es noch jemanden, der eine Vermischung von Bedenken in den STL-Streams sieht? Ein Stream dient normalerweise zum Lesen oder Schreiben von Bytes und sonst nichts. Eine Sache, die bestimmte Datentypen lesen oder schreiben kann, ist ein Formatierer (der möglicherweise keinen Stream zum Lesen / Schreiben der formatierten Bytes verwenden muss). Das Mischen beider in eine Klasse macht es noch komplexer, eigene Streams zu implementieren.
mmmmmmmm
4
@rsteven, es gibt eine Trennung dieser Bedenken. std::streambufist die Basisklasse zum Lesen und Schreiben von Bytes und istream/ ostreamoder zum formatierten Ein- und Ausgeben, wobei ein Zeiger std::streambufals Ziel / Quelle verwendet wird.
Johannes Schaub - litb
1
@litb: Aber ist es möglich, den vom Stream verwendeten Streambuf (Formatierer) zu wechseln? Vielleicht möchte ich die STL-Formatierung verwenden, aber die Daten über einen bestimmten Streambuf schreiben?
mmmmmmmm
2
@ rstevens,ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Johannes Schaub - litb

Antworten:

31

Mehrere unausgereifte Ideen fanden ihren Weg in den Standard: auto_ptr, vector<bool>, valarrayund export, um nur einige zu nennen. Daher würde ich die Anwesenheit von IOStreams nicht unbedingt als Zeichen für qualitativ hochwertiges Design betrachten.

IOStreams haben eine wechselvolle Geschichte. Sie sind eigentlich eine Überarbeitung einer früheren Stream-Bibliothek, wurden jedoch zu einer Zeit verfasst, als viele der heutigen C ++ - Redewendungen nicht existierten, sodass die Designer im Nachhinein keinen Vorteil hatten. Ein Problem, das sich erst im Laufe der Zeit bemerkbar machte, war, dass es fast unmöglich ist, IOStreams so effizient wie das stdio von C zu implementieren, da virtuelle Funktionen in großem Umfang verwendet und an interne Pufferobjekte selbst bei feinster Granularität weitergeleitet werden, und auch dank einer unergründlichen Fremdheit in der Art und Weise, wie Gebietsschemas definiert und implementiert werden. Ich gebe zu, dass meine Erinnerung daran ziemlich verschwommen ist. Ich erinnere mich, dass es vor einigen Jahren Gegenstand intensiver Debatten über comp.lang.c ++. Moderiert wurde.

Marcelo Cantos
quelle
3
Danke für deinen Beitrag. Ich werde das comp.lang.c++.moderatedArchiv durchsuchen und Links am Ende meiner Frage posten, wenn ich etwas Wertvolles finde. - Außerdem kann ich Ihnen nicht zustimmen auto_ptr: Nach dem Lesen von Herb Sutters Exceptional C ++ scheint es eine sehr nützliche Klasse bei der Implementierung des RAII-Musters zu sein.
stakx - trägt nicht mehr
5
@stakx: Trotzdem wird es veraltet und durch eine unique_ptrklarere und leistungsfähigere Semantik ersetzt.
Onkel Bens
3
@UncleBens unique_ptrbenötigt eine Wertreferenz. An dieser Stelle auto_ptrist also ein sehr leistungsfähiger Zeiger.
Artyom
7
Aber auto_ptrhat die Kopier- / Zuweisungssemantik verschraubt, was es zu einer Nische für die Dereferenzierung von Fehlern macht ...
Matthieu M.
5
@TokenMacGuy: Es ist kein Vektor und es werden keine Bools gespeichert. Das macht es etwas irreführend. ;)
Jalf
40

In Bezug darauf, wer sie entworfen hat, wurde die ursprüngliche Bibliothek (nicht überraschend) von Bjarne Stroustrup erstellt und dann von Dave Presotto neu implementiert. Dies wurde dann von Jerry Schwarz für Cfront 2.0 unter Verwendung der Idee von Manipulatoren von Andrew Koenig neu gestaltet und erneut implementiert. Die Standardversion der Bibliothek basiert auf dieser Implementierung.

Quelle "Das Design und die Entwicklung von C ++", Abschnitt 8.3.1.

Quuxpluson
quelle
3
@Neil - Nuss, was ist deine Meinung zum Design? Basierend auf Ihren anderen Antworten würden viele Leute gerne Ihre Meinung hören ...
DVK
1
@ DVK Habe gerade meine Meinung als separate Antwort gepostet.
2
Ich habe gerade eine Abschrift eines Interviews mit Bjarne Stroustrup gefunden, in dem er einige Teile der IOStreams-Geschichte erwähnt: www2.research.att.com/~bs/01chinese.html (dieser Link scheint momentan vorübergehend unterbrochen zu sein, aber Sie können es versuchen Googles Seiten-Cache)
stakx - trägt nicht mehr
2
Aktualisierter Link: stroustrup.com/01chinese.html .
FrankHB
28

Wenn Sie nach den heutigen Software-Engineering-Standards urteilen müssten (wenn tatsächlich eine allgemeine Übereinstimmung darüber besteht), würden die IOStreams von C ++ immer noch als gut konzipiert angesehen? (Ich möchte meine Software-Design-Fähigkeiten nicht durch etwas verbessern, das allgemein als veraltet gilt.)

Ich würde aus mehreren Gründen NEIN sagen :

Schlechte Fehlerbehandlung

Fehlerzustände sollten mit Ausnahmen gemeldet werden, nicht mit operator void*.

Das Anti-Muster "Zombie-Objekt" verursacht solche Fehler .

Schlechte Trennung zwischen Formatierung und E / A.

Dies macht Stream-Objekte unnötig komplex, da sie zusätzliche Statusinformationen für die Formatierung enthalten müssen, unabhängig davon, ob Sie diese benötigen oder nicht.

Es erhöht auch die Wahrscheinlichkeit, Fehler zu schreiben wie:

using namespace std; // I'm lazy.
cout << hex << setw(8) << setfill('0') << x << endl;
// Oops!  Forgot to set the stream back to decimal mode.

Wenn Sie stattdessen etwas geschrieben haben wie:

cout << pad(to_hex(x), 8, '0') << endl;

Es würde keine formatierungsbezogenen Statusbits geben und kein Problem.

Beachten Sie, dass in „modernen“ Sprachen wie Java, C # und Python, werden alle Objekte haben eine toString/ ToString/ __str__Funktion , die durch die I / O - Routinen aufgerufen wird. AFAIK, nur C ++ macht es umgekehrt, indem es stringstreamals Standardmethode für die Konvertierung in einen String verwendet wird.

Schlechte Unterstützung für i18n

Die Iostream-basierte Ausgabe teilt String-Literale in Teile auf.

cout << "My name is " << name << " and I am " << occupation << " from " << hometown << endl;

Format-Strings setzen ganze Sätze in String-Literale.

printf("My name is %s and I am %s from %s.\n", name, occupation, hometown);

Der letztere Ansatz lässt sich leichter an Internationalisierungsbibliotheken wie GNU gettext anpassen, da die Verwendung ganzer Sätze den Übersetzern mehr Kontext bietet. Wenn Ihre Zeichenfolgenformatierungsroutine die Neuordnung unterstützt (wie die POSIX $printf-Parameter), werden auch Unterschiede in der Wortreihenfolge zwischen den Sprachen besser behandelt.

dan04
quelle
4
Tatsächlich sollten für i18n Ersetzungen durch Positionen (% 1,% 2, ..) identifiziert werden, da eine Übersetzung möglicherweise eine Änderung der Parameterreihenfolge erfordert. Ansonsten stimme ich voll und ganz zu - +1.
Peterchen
4
@peterchen: Dafür sind die POSIX- $Spezifizierer da printf.
Jamesdlin
2
Das Problem sind nicht Formatzeichenfolgen, sondern dass C ++ nicht typsichere Variablen enthält.
dan04
5
Ab C ++ 11 gibt es jetzt typsichere Variablen.
Mooing Duck
2
IMHO ist die "zusätzliche Statusinformation" das schlimmste Problem. cout ist global; Durch das Anhängen von Formatierungsflags werden diese Flags global, und wenn man bedenkt, dass die meisten Verwendungen einen beabsichtigten Umfang von wenigen Zeilen haben, ist das ziemlich schrecklich. Es wäre möglich, dies mit einer 'Formatierer'-Klasse zu beheben, die an einen Ostream bindet, aber ihren eigenen Zustand beibehält. Und Dinge, die mit cout gemacht wurden, sehen im Allgemeinen schrecklich aus im Vergleich zu den gleichen Dingen, die mit printf gemacht wurden (wenn das möglich ist).
Greggo
17

Ich poste dies als separate Antwort, weil es reine Meinung ist.

Das Ausführen von Ein- und Ausgängen (insbesondere Eingaben) ist ein sehr, sehr schwieriges Problem. Daher überrascht es nicht, dass die iostreams-Bibliothek voller Bodges und Dinge ist, die im Nachhinein besser hätten gemacht werden können. Aber es scheint mir, dass alle E / A-Bibliotheken, in welcher Sprache auch immer, so sind. Ich habe noch nie eine Programmiersprache verwendet, in der das E / A-System etwas Schönes war, das mich in Ehrfurcht vor seinem Designer stehen ließ. Die iostreams-Bibliothek hat Vorteile, insbesondere gegenüber der CI / O-Bibliothek (Erweiterbarkeit, Typensicherheit usw.), aber ich glaube nicht, dass irgendjemand sie als Beispiel für großartiges OO oder generisches Design hochhält.


quelle
16

Meine Meinung zu C ++ iostreams hat sich im Laufe der Zeit erheblich verbessert, insbesondere nachdem ich begonnen hatte, sie tatsächlich durch die Implementierung meiner eigenen Stream-Klassen zu erweitern. Ich begann die Erweiterbarkeit und das Gesamtdesign zu schätzen, trotz der lächerlich schlechten Namen der Mitgliedsfunktionen wie xsputnoder was auch immer. Unabhängig davon denke ich, dass E / A-Streams eine massive Verbesserung gegenüber C stdio.h darstellen, das keine Typensicherheit bietet und mit großen Sicherheitslücken behaftet ist.

Ich denke, das Hauptproblem bei E / A-Streams besteht darin, dass sie zwei verwandte, aber etwas orthogonale Konzepte miteinander verbinden: Textformatierung und Serialisierung. Einerseits sind E / A-Streams so konzipiert, dass sie eine für Menschen lesbare, formatierte Textdarstellung eines Objekts erzeugen und andererseits ein Objekt in ein tragbares Format serialisieren. Manchmal sind diese beiden Ziele ein und dasselbe, aber manchmal führt dies zu ernsthaften lästigen Inkongruenzen. Beispielsweise:

std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;

...

std::string input_string;
ss >> input_string;
std::cout << input_string;

Was wir hier als Input bekommen, ist nicht was wir ursprünglich in den Stream ausgegeben haben. Dies liegt daran, dass der <<Operator die gesamte Zeichenfolge ausgibt, während der >>Operator nur aus dem Stream liest, bis er auf ein Leerzeichen stößt, da im Stream keine Längeninformationen gespeichert sind. Obwohl wir ein Zeichenfolgenobjekt mit "Hallo Welt" ausgeben, geben wir nur ein Zeichenfolgenobjekt mit "Hallo" ein. Während der Stream seinen Zweck als Formatierungsfunktion erfüllt hat, konnte er das Objekt nicht ordnungsgemäß serialisieren und dann unserialisieren.

Sie könnten sagen, dass E / A-Streams nicht als Serialisierungsfunktionen konzipiert wurden, aber wenn dies der Fall ist, wofür sind Eingabestreams wirklich gedacht ? Außerdem werden in der Praxis häufig E / A-Streams zum Serialisieren von Objekten verwendet, da es keine anderen Standard-Serialisierungsfunktionen gibt. Erwägenboost::date_time oder boost::numeric::ublas::matrix, wenn Sie ein Matrixobjekt mit dem <<Operator ausgeben , erhalten Sie genau die gleiche Matrix, wenn Sie es mit dem >>Operator eingeben. Um dies zu erreichen, mussten die Boost-Designer Informationen zur Spaltenanzahl und Zeilenanzahl als Textdaten in der Ausgabe speichern, was die tatsächlich lesbare Anzeige beeinträchtigt. Wieder eine umständliche Kombination aus Textformatierungsfunktionen und Serialisierung.

Beachten Sie, wie die meisten anderen Sprachen diese beiden Einrichtungen trennen. In Java erfolgt die Formatierung beispielsweise über dastoString() Methode, während die Serialisierung über die SerializableSchnittstelle erfolgt.

Meiner Meinung nach wäre die beste Lösung gewesen einzuführen Byte basierten Streams neben den Standardzeichen basieren Streams. Diese Streams würden mit Binärdaten arbeiten, ohne Bedenken hinsichtlich einer für Menschen lesbaren Formatierung / Anzeige. Sie können ausschließlich als Serialisierungs- / Deserialisierungsfunktionen verwendet werden, um C ++ - Objekte in tragbare Bytesequenzen zu übersetzen.

Charles Salvia
quelle
danke für die Antwort. Ich könnte mich in dieser Hinsicht irren, aber in Bezug auf Ihren letzten Punkt (bytebasierte vs. zeichenbasierte Streams) ist die (teilweise?) Antwort von IOStream nicht die Trennung zwischen Stream-Puffern (Zeichenkonvertierung, Transport und Pufferung). und Streams (Formatieren / Parsen)? Und könnten Sie keine neuen Stream-Klassen erstellen, die ausschließlich für (maschinenlesbare) Serialisierung und Deserialisierung gedacht sind, und andere, die ausschließlich auf (für Menschen lesbare) Formatierung und Analyse ausgerichtet sind?
stakx - trägt nicht mehr am
@stakx, ja, und tatsächlich habe ich das getan. Es ist etwas ärgerlicher als es sich anhört, da std::char_traitsman sich nicht tragbar darauf spezialisieren kann, eine zu nehmen unsigned char. Es gibt jedoch Problemumgehungen, sodass ich denke, dass die Erweiterbarkeit erneut Abhilfe schafft. Aber ich denke, die Tatsache, dass bytebasierte Streams nicht Standard sind, ist eine Schwäche der Bibliothek.
Charles Salvia
4
Um binäre Streams zu implementieren, müssen Sie neue Stream-Klassen und neue Pufferklassen implementieren , da Formatierungsprobleme nicht vollständig von diesen getrennt sind std::streambuf. Das einzige, was Sie erweitern, ist die std::basic_iosKlasse. Es gibt also eine Linie, in der "Erweitern" in das Gebiet "Vollständige Neuimplementierung" übergeht, und das Erstellen eines Binärstroms aus den C ++ - E / A-Stream-Funktionen scheint sich diesem Punkt zu nähern.
Charles Salvia
gut gesagt und genau das, was ich vermutet habe. Und die Tatsache, dass sowohl C als auch C ++ große Anstrengungen unternehmen, um keine Garantien für bestimmte Bitbreiten und Darstellungen zu geben, kann in der Tat problematisch werden, wenn es um E / A geht.
stakx - trägt nicht mehr am
" um ein Objekt in ein tragbares Format zu serialisieren. " Nein, sie sollten das nie unterstützen
neugieriger Kerl
11

Ich fand C ++ IOStreams immer schlecht designt: Ihre Implementierung macht es sehr schwierig, einen neuen Typ eines Streams richtig zu definieren. Sie mischen auch Io-Funktionen und Formatierungsfunktionen (denken Sie an Manipulatoren).

Persönlich liegt das beste Stream-Design und die beste Implementierung, die ich je gefunden habe, in der Ada-Programmiersprache. Es ist ein Modell für die Entkopplung, eine Freude daran, neue Arten von Streams zu erstellen, und Ausgabefunktionen funktionieren immer, unabhängig vom verwendeten Stream. Dies ist einem kleinsten gemeinsamen Nenner zu verdanken: Sie geben Bytes an einen Stream aus und das wars. Stream-Funktionen sorgen dafür, dass die Bytes in den Stream eingefügt werden. Es ist nicht ihre Aufgabe, z. B. eine Ganzzahl hexadezimal zu formatieren (natürlich gibt es eine Reihe von Typattributen, die einem Klassenmitglied entsprechen und für die Formatierung definiert sind).

Ich wünschte, C ++ wäre so einfach in Bezug auf Streams ...

Adrien Plisson
quelle
Das erwähnte Buch erklärt die grundlegende IOStreams-Architektur wie folgt: Es gibt eine Transportschicht (die Stream- Pufferklassen ) und eine Analyse- / Formatierungsschicht (die Stream-Klassen). Die ersteren sind für das Lesen / Schreiben von Zeichen aus / in einen Bytestream verantwortlich, während die letzteren für das Parsen von Zeichen oder das Serialisieren von Werten in Zeichen verantwortlich sind. Dies scheint klar genug zu sein, aber ich bin mir nicht sicher, ob diese Bedenken in der Realität wirklich klar voneinander getrennt sind, insb. wenn Gebietsschemas ins Spiel kommen. - Ich stimme Ihnen auch in der Schwierigkeit zu, neue Streams-Klassen zu implementieren.
stakx - nicht mehr
"Mix Io Features und Formatierungs Features" <- Was ist daran falsch? Das ist der Sinn der Bibliothek. Wenn Sie neue Streams erstellen möchten, sollten Sie einen Streambuf anstelle eines Streams erstellen und einen einfachen Stream um den Streambuf erstellen.
Billy ONeal
Es scheint, dass Antworten auf diese Frage mich etwas verstehen ließen, das mir nie erklärt wurde: Ich sollte einen Streambuf anstelle eines Streams ableiten ...
Adrien Plisson
@stakx: Wenn die Streambuf-Ebene das tun würde, was Sie gesagt haben, wäre es in Ordnung. Die Konvertierung zwischen Zeichenfolge und Byte ist jedoch alle mit der tatsächlichen E / A (Datei, Konsole usw.) verwechselt. Es gibt keine Möglichkeit, die Datei-E / A auszuführen, ohne auch die Zeichenkonvertierung durchzuführen, was sehr unglücklich ist.
Ben Voigt
10

Ich denke, das Design von IOStreams ist in Bezug auf Erweiterbarkeit und Nützlichkeit brillant.

  1. Stream-Puffer: Schauen Sie sich die Erweiterungen von boost.iostream an: Erstellen Sie gzip, tee, kopieren Sie Streams in wenigen Zeilen, erstellen Sie spezielle Filter und so weiter. Ohne das wäre es nicht möglich.
  2. Lokalisierungsintegration und Formatierungsintegration. Sehen Sie, was getan werden kann:

    std::cout << as::spellout << 100 << std::endl;

    Kann drucken: "einhundert" oder sogar:

    std::cout << translate("Good morning")  << std::endl;

    Kann "Bonjour" oder "בוקר טוב" je nach Gebietsschema drucken std::cout !

    Solche Dinge können nur getan werden, weil iostreams sehr flexibel sind.

Könnte es besser gemacht werden?

Natürlich könnte es! In der Tat gibt es viele Dinge, die verbessert werden könnten ...

Heutzutage ist es ziemlich schmerzhaft, korrekt abzuleiten stream_buffer. Es ist nicht trivial, zusätzliche Formatierungsinformationen zum Streaming hinzuzufügen, aber möglich.

Aber im Rückblick vor vielen Jahren war das Bibliotheksdesign immer noch gut genug, um viele Leckereien mitzubringen.

Weil Sie nicht immer das große Ganze sehen können, aber wenn Sie Punkte für Erweiterungen hinterlassen, erhalten Sie viel bessere Fähigkeiten, selbst in Punkten, an die Sie nicht gedacht haben.

Artyom
quelle
5
Können Sie einen Kommentar dazu abgeben, warum Ihre Beispiele für Punkt 2 besser wären, als einfach so etwas wie print (spellout(100));und zu verwenden? print (translate("Good morning"));Dies scheint eine gute Idee zu sein, da dies die Formatierung und i18n von E / A entkoppelt.
Schedler
3
Weil es nach Sprache übersetzt werden kann, die in Strom durchdrungen ist. dh : french_output << translate("Good morning"); english_output << translate("Good morning") würde Ihnen geben: "Bonjour Guten Morgen"
Artyom
3
Die Lokalisierung ist viel schwieriger, wenn Sie '<< "text" << value' in einer Sprache, aber '<< value << "text"' in einer anderen Sprache ausführen müssen - im Vergleich zu printf
Martin Beckett
@ Martin Beckett Ich weiß, werfen Sie einen Blick auf die Boost.Locale-Bibliothek, was passiert, wenn Sie dies tun out << format("text {1}") % valueund es möglicherweise übersetzt wird "{1} translated". Also funktioniert es gut ;-).
Artyom
15
Was "getan werden kann", ist nicht sehr relevant. Sie sind Programmierer, alles kann mit genügend Aufwand erledigt werden . Aber IOStreams macht es furchtbar schmerzhaft, das meiste zu erreichen, was getan werden kann . Und normalerweise bekommst du schlechte Leistung für deine Probleme.
Jalf
2

(Diese Antwort basiert nur auf meiner Meinung)

Ich denke, dass IOStreams viel komplexer sind als ihre Funktionsäquivalente. Wenn ich in C ++ schreibe, verwende ich immer noch die cstdio-Header für "alte" E / A, die ich viel vorhersehbarer finde. Nebenbei bemerkt (obwohl es nicht wirklich wichtig ist; der absolute Zeitunterschied ist vernachlässigbar) wurde IOStreams mehrfach als langsamer als CI / O erwiesen.

Delan Azabani
quelle
Ich denke, Sie meinen eher "Funktion" als "funktional". Die funktionale Programmierung erzeugt Code, der noch schlechter aussieht als die generische Programmierung.
Chris Becke
Vielen Dank, dass Sie auf diesen Fehler hingewiesen haben. Ich habe die Antwort bearbeitet, um die Korrektur widerzuspiegeln.
Delan Azabani
5
IOStreams müssten mit ziemlicher Sicherheit langsamer sein als klassisches stdio; Wenn ich die Aufgabe erhalten würde, ein erweiterbares und benutzerfreundliches E / A-Streams-Framework zu entwerfen, würde ich die Geschwindigkeit wahrscheinlich als zweitrangig beurteilen, da die tatsächlichen Engpässe wahrscheinlich die Datei-E / A-Geschwindigkeit oder die Bandbreite des Netzwerkverkehrs sind.
stakx - trägt nicht mehr
1
Ich bin damit einverstanden, dass für E / A oder Netzwerk die Rechengeschwindigkeit nicht so wichtig ist. Denken Sie jedoch daran, dass C ++ für die Konvertierung von Zahlen und Zeichenfolgen verwendet wird sstringstream. Ich denke, Geschwindigkeit spielt eine Rolle, obwohl sie zweitrangig ist.
Matthieu M.
1
@stakx-Datei-E / A und Netzwerkengpässe sind eine Funktion der Kosten pro Byte, die recht gering sind und durch technologische Verbesserungen drastisch gesenkt werden. Angesichts von DMA nehmen diese Overheads anderen Threads auf demselben Computer keine CPU-Zeit. Wenn Sie also eine formatierte Ausgabe ausführen, können die Kosten für eine effiziente oder nicht effiziente Ausführung leicht erheblich sein (zumindest nicht von der Festplatte oder dem Netzwerk überschattet; wahrscheinlicher wird sie von anderen Verarbeitungen in der App überschattet).
Greggo
2

Ich stoße immer auf Überraschungen, wenn ich den IOStream benutze.

Die Bibliothek scheint textorientiert und nicht binär orientiert zu sein. Das mag die erste Überraschung sein: Die Verwendung des Binärflags in Dateistreams reicht nicht aus, um ein Binärverhalten zu erhalten. Der Benutzer Charles Salvia oben hat es richtig beobachtet: IOStreams mischt Formatierungsaspekte (wo Sie eine hübsche Ausgabe wünschen, z. B. begrenzte Ziffern für Floats) mit Serialisierungsaspekten (wo Sie keinen Informationsverlust wollen). Wahrscheinlich wäre es gut, diese Aspekte zu trennen. Boost.Serialization macht diese Hälfte. Sie haben eine Serialisierungsfunktion, die bei Bedarf zu den Insertern und Extraktoren weitergeleitet wird. Dort haben Sie bereits die Spannung zwischen beiden Aspekten.

Viele Funktionen haben auch eine verwirrende Semantik (z. B. abrufen, abrufen, ignorieren und lesen. Einige extrahieren das Trennzeichen, andere nicht; andere setzen eof). Einige erwähnen außerdem die seltsamen Funktionsnamen bei der Implementierung eines Streams (z. B. xsputn, uflow, underflow). Noch schlimmer wird es, wenn man die wchar_t-Varianten verwendet. Der Wifstream führt eine Übersetzung in Multibyte durch, der Wstringstream jedoch nicht. Binäre E / A funktioniert mit wchar_t nicht sofort: Sie müssen den Codecvt überschreiben.

Die c-gepufferte E / A (dh DATEI) ist nicht so leistungsfähig wie das C ++ - Gegenstück, aber transparenter und weist ein viel weniger kontraintuitives Verhalten auf.

Immer noch, wenn ich über den IOStream stolpere, fühle ich mich wie eine Motte zum Feuern davon angezogen. Wahrscheinlich wäre es eine gute Sache, wenn sich ein wirklich kluger Kerl die gesamte Architektur genauer ansehen würde.

gast128
quelle
1

Ich kann nicht anders, als den ersten Teil der Frage zu beantworten (Wer hat das getan?). Aber es wurde in anderen Beiträgen beantwortet.

Was den zweiten Teil der Frage betrifft (gut gestaltet?), Lautet meine Antwort ein klares "Nein!". Hier ein kleines Beispiel, das mich seit Jahren ungläubig den Kopf schütteln lässt:

#include <stdint.h>
#include <iostream>
#include <vector>

// A small attempt in generic programming ;)
template <class _T>
void ShowVector( const char *title, const std::vector<_T> &v)
{
    std::vector<_T>::const_iterator iter;
    std::cout << title << " (" << v.size() << " elements): ";
    for( iter = v.begin(); iter != v.end(); ++iter )
    {
        std::cout << (*iter) << " ";
    }
    std::cout << std::endl;
}
int main( int argc, const char * argv[] )
{
    std::vector<uint8_t> byteVector;
    std::vector<uint16_t> wordVector;
    byteVector.push_back( 42 );
    wordVector.push_back( 42 );
    ShowVector( "Garbled bytes as characters output o.O", byteVector );
    ShowVector( "With words, the numbers show as numbers.", wordVector );
    return 0;
}

Der obige Code erzeugt aufgrund des iostream-Designs Unsinn. Aus einigen Gründen, die ich nicht verstehen kann, behandeln sie uint8_t-Bytes als Zeichen, während größere Integraltypen wie Zahlen behandelt werden. Qed Schlechtes Design.

Ich kann mir auch keine Möglichkeit vorstellen, dies zu beheben. Der Typ könnte stattdessen auch ein Float oder ein Double sein ... also hilft eine Umwandlung in 'int', um albernen iostream verständlich zu machen, dass Zahlen und keine Zeichen das Thema sind, nicht weiter.

Nachdem ich eine Antwort auf meine Antwort erhalten habe, vielleicht noch ein paar Worte zur Erklärung ... Das IOStream-Design ist fehlerhaft, da es dem Programmierer nicht die Möglichkeit gibt, anzugeben, wie ein Element behandelt wird. Die IOStream-Implementierung trifft willkürliche Entscheidungen (z. B. die Behandlung von uint8_t als Zeichen und nicht als Bytenummer). Dies ist ein Fehler des IOStream-Designs, da sie versuchen, das Unerreichbare zu erreichen.

In C ++ kann ein Typ nicht klassifiziert werden - die Sprache verfügt nicht über die entsprechende Funktion. Es gibt keine is_number_type () oder is_character_type (), mit denen IOStream eine vernünftige automatische Auswahl treffen könnte. Das zu ignorieren und zu versuchen, mit dem Raten davonzukommen, ist ein Konstruktionsfehler einer Bibliothek.

Zugegeben, printf () würde in einer generischen "ShowVector ()" - Implementierung ebenfalls nicht funktionieren. Dies ist jedoch keine Entschuldigung für das Verhalten von iostream. Es ist jedoch sehr wahrscheinlich, dass ShowVector () im Fall printf () folgendermaßen definiert wird:

template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );
BitTickler
quelle
3
Die Schuld liegt nicht (rein) bei iostream. Überprüfen Sie, wofür Sie uint8_tein typedef sind . Ist es eigentlich ein Char? Dann beschuldigen Sie iostreams nicht, es wie einen Saibling behandelt zu haben.
Martin Ba
Wenn Sie sicherstellen möchten, dass Sie eine Nummer im generischen Code erhalten, können Sie die num_putFacette anstelle des Operators zum Einfügen von Streams verwenden.
Martin Ba
@Martin Ba Sie haben Recht - c / c ++ - Standards lassen offen, wie viele Bytes ein "kurzes int ohne Vorzeichen" hat. "unsigned char" ist eine Eigenart der Sprache. Wenn Sie wirklich ein Byte möchten, müssen Sie ein vorzeichenloses Zeichen verwenden. C ++ erlaubt es auch nicht, Vorlagenargumente einzuschränken - wie "nur Zahlen". Wenn ich also die Implementierung von ShowVector auf Ihre vorgeschlagene num_put-Lösung ändere, kann ShowVector keinen Vektor von Zeichenfolgen mehr anzeigen, oder? ;)
BitTickler
1
@Martin Bla: cppreference erwähnt, dass int8_t ein vorzeichenbehafteter Integer-Typ mit einer Breite von genau 8 Bit ist. Ich stimme dem Autor zu, dass es seltsam ist, dass Sie dann eine Müllausgabe erhalten, obwohl dies technisch durch das typedef und die Überladung von char-Typen in iostream erklärt werden kann . Es hätte gelöst werden können, indem ein __int8 ein echter Typ anstelle eines typedef gewesen wäre.
Gast128
Oh, es ist eigentlich ziemlich einfach zu beheben: // Korrekturen für std :: ostream, die die Unterstützung für vorzeichenlose / signierte / char-Typen // unterbrochen haben und 8-Bit-Ganzzahlen drucken, als wären sie Zeichen. Namespace ostream_fixes {inline std :: ostream & operator << (std :: ostream & os, vorzeichenloses Zeichen i) {return os << static_cast <vorzeichenloses int> (i); } inline std :: ostream & operator << (std :: ostream & os, signiertes Zeichen i) {return os << static_cast <signiertes int> (i); }} // Namespace ostream_fixes
mcv
1

C ++ - Iostreams weisen viele Fehler auf, wie in den anderen Antworten angegeben, aber ich möchte etwas zu ihrer Verteidigung beachten.

C ++ ist unter den ernsthaft verwendeten Sprachen praktisch einzigartig und macht die Eingabe und Ausgabe von Variablen für Anfänger unkompliziert. In anderen Sprachen beinhaltet die Benutzereingabe in der Regel Typzwang oder Zeichenfolgenformatierer, während C ++ den Compiler die ganze Arbeit erledigen lässt. Das gleiche gilt weitgehend für die Ausgabe, obwohl C ++ in dieser Hinsicht nicht so einzigartig ist. Trotzdem können Sie formatierte E / A in C ++ ziemlich gut ausführen, ohne Klassen und objektorientierte Konzepte verstehen zu müssen, was pädagogisch nützlich ist, und ohne die Formatsyntax verstehen zu müssen. Auch wenn Sie Anfänger unterrichten, ist das ein großes Plus.

Diese Einfachheit für Anfänger hat ihren Preis, was den Umgang mit E / A in komplexeren Situationen zu Kopfschmerzen machen kann. Hoffentlich hat der Programmierer zu diesem Zeitpunkt genug gelernt, um mit ihnen umgehen zu können, oder ist zumindest alt genug geworden trinken.

user2310967
quelle