Klasse in Datei/Stream codieren
-
Hallo zusammen,
ich hadere mit einem kleinen Problem:
Zunächst habe ich eine Klasse definiert und möchte nun dafür sorgen, dass Instanzen dieser Klasse in Strings codiert, in Dateien abgespeichert und wieder ausgelesen werden können.
[*] Im Internet habe ich nun viele Hinweise dazu gesehen, wie man das umsetzten kann; oft ohne eigene Definition der Codierung, sondern meist mit impliziten oder expliziten Casts, um die sich der Programmierer nicht selbst kümmert:
stream.write((char *)&instanz, sizeof(instanz));[*] Mir ist dabei aufgefallen, dass es sich in allen solchen Beispielen, die ich gefunden habe, um Klassen mit festem Speicherbedarf handelt: Die statischen Members waren stets von Typen wie char[999].
Kann eine solche Lösung auch funktionieren, wenn die Klasse Members vom Typ int oder double hat?
Bei den obigen Anweisungen wird ja implizit in Strings umgewandelt, und die Länge des Strings, in den ich eine Zahl umwandle, hängt schließlich stark von dieser Zahl ab. Insbesondere kann m.E. der lesende Stream hinterher nicht mehr unterscheiden, wo z.B. in folgender Zeile in der Datei ein int-Wert aufhört und ein Double beginnt:... 8698402884909809.533456Ein Trennzeichen wird hier gebraucht, oder aber das Auffüllen der Zahlenwerte auf eine feste Länge.
[*] Eine Alternative, die ich umsetzen kann, wäre die Verwendung eines Zeilenumbruchs (endl) als Trennzeichen. Ich könnte dann wie folgt lesen und schreiben:
stream << instanz.member1; stream << endl; stream << instanz.member2; stream << endl; ... std::string zeile; anderer_stream.getline(zeile, HINREICHEND_GROSSE_KONSTANTE); int wert1(zeile); anderer_stream.getline(zeile, HINREICHEND_GROSSE_KONSTANTE); double wert2(zeile); ... klasse neue_instanz(wert1, wert2, ...);[*] Diese Lösung tut eigentlich komplett, was ich möchte. Allerdings möchte ich eigentlich erreichen, dass die Zeile der Datei genau den abgespeicherten Instanzen entsprechen, d.h. der Zeilenumbruch als Trennzeichen zwischen Instanzen, nicht den Members dieser Instanzen.
Was ich also eigentlich suche, sind Funktionen wievoid fstream::write_int(int) int fstream::read_int() void fstream::write_double(double) double fstream::write_double()usw. Mit anderen Worten: Ich bin gern bereit, selbst zu definieren, in welcher Reihenfolge die Members herausgeschrieben werden. Ich möchte mich aber eigentlich nicht um den genauen Speicherbedarf in Bytes für jedes Member noch um explizites Einfügen und Suchen von Trennzeichen kümmern.
Gibt es da eine Lösung?
Ich nehme an, dieses Problem ist heute nicht gerade zum ersten Male in der Geschichte der Programmierung aufgetaucht. Eigentlich hätte ich gedacht, über einfache Web-Suche Hinweise dazu zu bekommen - leider ist mir das nicht gelungen. Vielen Dank, wenn Ihr mir weiterhelft!
-
Solange die einzelnen Elemente direkt in der Klasse untergebracht sind, werden sie von dem write()-Aufruf mit erfasst. Das gilt nicht nur für char-Arrays, auch für int oder double - die jeweils in ihrer Binär-Repräsentation geschrieben werden.
Bei dynamischen Datenstrukturen (Zeiger oder STL-Container) erwischst du mit dem write() nur die Zeiger, aber nicht die Nutzdaten, die außerhalb der Instanz gespeichert werden.Die Stream-Operatoren schreiben alles, was du benötigst, in Textform in die Datei, das hat den Vorteil, daß du selber die Reihenfolge aussuchen und auch "extern" gespeicherte Daten unterbringen kannst. Was du da als Trennzeichen zwischen den einzelnen Feldern verwendest, bleibt ganz dir überlassen (das muß also nicht der Zeilenwechsel sein):
ostream& operator<<(ostream& out,const myclass& data) { out << data.feld1 << ';' << data.feld2 << endl; return out; } istream& operator>>(istream& in,myclass& data) { char delim; in >> data.feld1 >> delim >> data.feld2; in.ignore(); return in; }(nur grob skizziert, in der fertigen Klasse solltest du noch Fehlerüberprüfungen etc einbauen)
-
tollelege schrieb:
Ich nehme an, dieses Problem ist heute nicht gerade zum ersten Male in der Geschichte der Programmierung aufgetaucht. Eigentlich hätte ich gedacht, über einfache Web-Suche Hinweise dazu zu bekommen - leider ist mir das nicht gelungen. Vielen Dank, wenn Ihr mir weiterhelft!
Im allgemeinsten Fall nennt man das Problem Serialisierung.
Es gibt sogar eine Boost-Bibliothek dazu.
-
CStoll schrieb:
Solange die einzelnen Elemente direkt in der Klasse untergebracht sind, werden sie von dem write()-Aufruf mit erfasst. Das gilt nicht nur für char-Arrays, auch für int oder double - die jeweils in ihrer Binär-Repräsentation geschrieben werden.
... also insbesondere auch in fester Länge, also ggf. mit führenden und ausgehenden Nullen?
(Das Problem mit den Zeigern ist mir bekannt.)
CStoll schrieb:
istream& operator>>(istream& in,myclass& data)
{
char delim;
in >> data.feld1 >> delim >> data.feld2;
in.ignore();
return in;
}Diese Code-Skizze verstehe ich leider nicht ganz:
D.h. auch die Textform ist mit fester Länge ausgestattet,
und der Operator braucht gar nicht vorher zu wissen, was das Trennzeichen ist (findet es aber nebenbei heraus)?Das wäre ja stark ...
-
tollelege schrieb:
CStoll schrieb:
Solange die einzelnen Elemente direkt in der Klasse untergebracht sind, werden sie von dem write()-Aufruf mit erfasst. Das gilt nicht nur für char-Arrays, auch für int oder double - die jeweils in ihrer Binär-Repräsentation geschrieben werden.
... also insbesondere auch in fester Länge, also ggf. mit führenden und ausgehenden Nullen?
Wie ich schon sagte, werden die Zahlen in Binärformat gespeichert, d.h. insbesondere auch mit konstanter Länge (idR 4 oder 8 Byte).
Diese Code-Skizze verstehe ich leider nicht ganz:
D.h. auch die Textform ist mit fester Länge ausgestattet,
und der Operator braucht gar nicht vorher zu wissen, was das Trennzeichen ist (findet es aber nebenbei heraus)?Nein, die Form hat keine feste Länge - der Eingabe-Operator für Zahlen liest solange, bis er ein unpassendes Zeichen findet (in dem Beispiel das Semikolon, das der Ausgabe-Operator geschrieben hat). Danach lese ich das Trennzeichen (im produktiven Code sollte noch kontrolliert werden, ob das richtige Trennzeichen dort kam) und das zweite Feld.
-
Donnerwetter - das hätte ich den Streams alles gar nicht zugetraut.
Vielen Dank für die Hilfe!
Werde das gleich mal ausprobieren.Vielen Dank auch Dir, Primärproblem, für die richtige Einordung!
-
Hm, ich muss leider doch noch mal nachfragen.
Ich habe jetzt ziemlich genau CStolls zweiten Vorschlag übernommen:
ostream operator<<(ostream& out, const klasse& instanz);Nun hat meine Klasse viele private Felder, die jeweils über eine öffentliche Funktion ausgelesen werden können, also z.B.
class klasse{ public: int zahl(); ... private: int izahl; ... }; klasse::zahl(){ return izahl; }Wenn ich nun in der Definition des oben deklarierten <<-Operators auf so etwas wie instanz.zahl() zugreife, führt das zu Problemen mit dem Schlüsselwort const im zweiten Argument, die mir geläufig sind, obwohl ich sie nicht verstehe - und die ich einfach aushebeln konnte, indem ich den Operator als Klassen-"Friend" definiert habe und direkt etwa auf instanz.izahl zugreife.
Nun hat die Klasse "klasse" aber auch ein Member einer anderen selbst definierten Klasse:
class klasse{ public: ... private: andere_klasse ak_member; ... };... und auf diese Klasse muss ich auch zugreifen:
std::string andere_klasse::toStr(){ ... }; ostream operator<<(ostream& out, const klasse& instanz) { ... out << instanz.ak_member.toStr(); ... }Dies jedoch veranlsst den Compiler zu etwas derberen Äußerungen:
/usr/include/c++/4.2.1/bits/ios_base.h: In copy constructor ‘std::basic_ios<char, std::char_traits<char> >::basic_ios(const std::basic_ios<char, std::char_traits<char> >&)’: /usr/include/c++/4.2.1/bits/ios_base.h:779: error: ‘std::ios_base::ios_base(const std::ios_base&)’ is private /usr/include/c++/4.2.1/iosfwd:55: error: within this context /usr/include/c++/4.2.1/iosfwd: In copy constructor ‘std::basic_ostream<char, std::char_traits<char> >::basic_ostream(const std::basic_ostream<char, std::char_traits<char> >&)’: /usr/include/c++/4.2.1/iosfwd:64: note: synthesized method ‘std::basic_ios<char, std::char_traits<char> >::basic_ios(const std::basic_ios<char, std::char_traits<char> >&)’ first required hereWas ist hier das Problem?
Und warum kann der Compiler nicht sagen: "Ihre Datei, Zeile 99: Aufruf von instanz.ak_member.toStr() widerspricht Qualifier 'const'", sondern muss sich auf interne Lib-Dateien berufen?Und heißt das, ich muss zuerst in analoger Weise eine Funktion
ostream operator<<(ostream& out, const andere_klasse& andere instanz);bauen?
-
ostream ist nicht kopierbar. Üblicherweise gibt man aus operator<< das Eingabe-Streamobjekt per Referenz wieder zurück:
ostream /* --> */ & /* <-- */ operator<<(ostream& out, const andere_klasse& andere_instanz) { ... return out; }
-
Und was das Problem mit dem "const" angeht - reine Getter solltest du sowieso als konstante Methoden definieren:
int Klasse::zahl() /* -> */ const;(es wird schließlich noch andere Situationen geben, wo du mit konstanten Objekten hatieren willst)
-
Hat getan, was es soll.
Vielen Dank!