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.)
std::streambuf
ist die Basisklasse zum Lesen und Schreiben von Bytes undistream
/ostream
oder zum formatierten Ein- und Ausgeben, wobei ein Zeigerstd::streambuf
als Ziel / Quelle verwendet wird.ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Antworten:
Mehrere unausgereifte Ideen fanden ihren Weg in den Standard:
auto_ptr
,vector<bool>
,valarray
undexport
, 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.
quelle
comp.lang.c++.moderated
Archiv durchsuchen und Links am Ende meiner Frage posten, wenn ich etwas Wertvolles finde. - Außerdem kann ich Ihnen nicht zustimmenauto_ptr
: Nach dem Lesen von Herb Sutters Exceptional C ++ scheint es eine sehr nützliche Klasse bei der Implementierung des RAII-Musters zu sein.unique_ptr
klarere und leistungsfähigere Semantik ersetzt.unique_ptr
benötigt eine Wertreferenz. An dieser Stelleauto_ptr
ist also ein sehr leistungsfähiger Zeiger.auto_ptr
hat die Kopier- / Zuweisungssemantik verschraubt, was es zu einer Nische für die Dereferenzierung von Fehlern macht ...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.
quelle
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:
Wenn Sie stattdessen etwas geschrieben haben wie:
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 esstringstream
als 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.
Format-Strings setzen ganze Sätze in String-Literale.
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.quelle
$
Spezifizierer daprintf
.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
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
xsputn
oder 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:
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ägen
boost::date_time
oderboost::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 das
toString()
Methode, während die Serialisierung über dieSerializable
Schnittstelle 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.
quelle
std::char_traits
man sich nicht tragbar darauf spezialisieren kann, eine zu nehmenunsigned 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.std::streambuf
. Das einzige, was Sie erweitern, ist diestd::basic_ios
Klasse. 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.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 ...
quelle
Ich denke, das Design von IOStreams ist in Bezug auf Erweiterbarkeit und Nützlichkeit brillant.
Lokalisierungsintegration und Formatierungsintegration. Sehen Sie, was getan werden kann:
Kann drucken: "einhundert" oder sogar:
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.
quelle
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.french_output << translate("Good morning")
;english_output << translate("Good morning")
würde Ihnen geben: "Bonjour Guten Morgen"out << format("text {1}") % value
und es möglicherweise übersetzt wird"{1} translated"
. Also funktioniert es gut;-)
.(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.
quelle
sstringstream
. Ich denke, Geschwindigkeit spielt eine Rolle, obwohl sie zweitrangig ist.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.
quelle
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:
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:
quelle
uint8_t
ein typedef sind . Ist es eigentlich ein Char? Dann beschuldigen Sie iostreams nicht, es wie einen Saibling behandelt zu haben.num_put
Facette anstelle des Operators zum Einfügen von Streams verwenden.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.
quelle