Eigene Klassen, die Pointer enthalten in Binary-Datei speichern?
-
Liebes Forum,
man kann ja mit ofstream.write()
auch eigene Datenstrukturen in einer Binarydatei speichern.
Was ist aber, wenn diese Datenstrukturen Zeiger auf Adressbereiche,
deren Inhalt ich mitspeichern möchte beinhalten?Beispiel:
struct xyz { int* a; int b; } xyz i; xyz j; i.a = new int[20]; j.a = new int[12]; //i.a und j.a werden noch mit Integer-Werten gefüllt datei.write((char*)&i, sizeof(i)); datei.write((char*)&j, sizeof(j));Wird hier jetzt der Inhalt der dynamisch erzeugten Integer-Arrays
mit in der Datei gespeichert, oder nur die Adresse?
Falls nicht, wie kann man dies erreichen?Wie kann man später die Strukturen wieder einlesen,
wenn ja jedes Objekt eine unterschiedliche Größe hat?Danke für die Antworten!
-
Lieber Richi,
dein Problem (also nicht das eigentliche aber trotzdem)ist
1. Du benutzt C-Casts und Arrays. >Schnüff<
2. Möglich wäre was "provisorisches":Du schreibst alle Zahlen rein und hast dann am Ende sowas ähnliches wie ein Delim Zeichen, also beim Einlesen solange die Struktur weiter mit den Eingelesenen Werten befüllen bis das Delim Zeichen auftaucht - dann ist die Struktur (edit: bzw. das a-Array) voll und du kannst mit der nächsten weitermachen (oder was immer du willst). Das größte Problem hierbei ist, dass du beim Schreiben/Lesen nicht wissen kannst, wie groß das Array j bzw. i.a ist, nimm lieber einen Vector (oder erweiter die Struktur um eine Eigenschaft 'size_of_a' o.ä.) .
Bin aber sicher es gibt noch bessere Lösungen.Was du im Beispiel gemacht hast, ist, die Adresse des Objekts der Struktur zu einem Char-Zeiger zu Casten und diese dann, anhand der Größe des Objekts, reinzuschreiben.
http://www.cplusplus.com/reference/iostream/ostream/write/

-
Am einfachsten ist, wenn du eine Funktion write machst, die das Ding in den Stream schreibst. Ausnahmsweise hat Hacker aber damit Recht, dass du einen Vector benutzen solltest. Das könnte in etwa so aussehen:
struct foo { std::vector<int> bar; int baz; void write(std::ostream& os) { auto size = bar.size(); os.write(reinterpret_cast<const char*>(&size), sizeof(size)); // Größe schreiben, um nacher zu wissen, wie viele Elemente gelesen werden sollen for(auto it = bar.begin(); it != bar.end(); ++it) os.write(reinterpret_cast<const char*>(&*it), sizeof(*it)); // Elemente schreiben os.write(reinterpret_cast<const char*>(&baz), sizeof(baz)); // baz schreiben } };Beim Einlesen musst du natürlich die Größe wieder einlesen und dann entsprechend oft Elemente.
-
314159265358979 schrieb:
int baz; //... os.write(reinterpret_cast<const char*>(baz), sizeof(baz)); // baz schreibenfail
-
Ich habe doch nur ein & vergessen, kann doch mal passieren Schnucki.
-
314159265358979 schrieb:
Ich habe doch nur ein & vergessen, kann doch mal passieren Schnucki.
Trotzdem 2 Casts zuviel.
-
Erklär mal bitte.
-
reinterpret_cast ist ein Konstrukt, dass sparsam eingesetzt werden sollte. Außerdem sollte man sich nicht unnötig wiederholen. Mithin ist eine eigene Funktion dafür angebracht. Erfahrungsgemäß werden gerade solche Codestücke wie oben gerne 1:1 in produktiven Code mit den entsprechenden Folgen übernommen.
Schöner wäre z.B.template <typename T> typename std::enable_if<std::is_fundamental<T>, std::ostream>::type& write_unformatted(std::ostream& os, const T& value) { os.write(&reinterpret_cast<const char&>(value), sizeof value); return os; }Damit werden automatisch erst einmal alle fundamentalen Typen erschlagen (nur für diese macht das ja einigermaßen Sinn). Für weitere Typen kann man dann geeignete Overloads anbieten (die logischerweise ohne Casts auskommen)
struct foo { std::vector<int> bar; int baz; }; template <typename T, typename A> ostream& write_unformatted(std::ostream& os, const std::vector<T, A>& v) { write_unformatted(os, v.size()); for(auto it = v.begin(); it != v.end(); ++it) write_unformatted(os, *it); return os; } ostream& write_unformatted(std::ostream& os, const foo& v) { write_unformatted(os, v.bar); write_unformatted(os, v.baz); return os; }
-
Naja, wenn du sowas meintest

-
Wenn schon auf Kleinigkeiten herum reiten, dann bitte so:
template <typename T> typename std::enable_if<std::is_fundamental<T>, std::ostream>::type& write_unformatted(std::ostream& os, const T& value) { void const* temp = static_cast<void const*>(&value); os.write(static_cast<char const*>(temp), sizeof(value)); return os; }
-
Das ist UB.
-
Ethon schrieb:
Wenn schon auf Kleinigkeiten herum reiten, dann bitte so:
template <typename T> typename std::enable_if<std::is_fundamental<T>, std::ostream>::type& write_unformatted(std::ostream& os, const T& value) { void const* temp = static_cast<void const*>(&value); os.write(static_cast<char const*>(temp), sizeof(value)); return os; }Warum? Es geht nicht darum, Dinge zu verstecken. Wenn es gefährlich (bzw. in irgenweiner Weise speziell) ist, sollte der Code das auch widerspiegeln. Umgekehrt geht es nicht an, sich diesbezgl. 100 mal zu wiederholen (gerne dann in 500-Zeilen-Monsterfunktionen). Dann wird nämlich die Aufmerksamkeit bei jeder einzelnen Zeile stark nachlassen.
-
Ich hab jediglich den Cast von reinterpret auf 2 statics geändert, was ja in der Theorie die sauberere & sichere Version ist.

-
Sieht interessant aus^^ Damit kann ich mir sicher etwas zusammenbasteln.
Was ist denn 'auto' für ein Datentyp? Ich kannte das nur als Präfix, um die Art der Speicherung anzugeben... also z.B. auto int a oder register int b ... so in dem Kontext halt...
-
Richi schrieb:
Sieht interessant aus^^ Damit kann ich mir sicher etwas zusammenbasteln.
Was ist denn 'auto' für ein Datentyp? Ich kannte das nur als Präfix, um die Art der Speicherung anzugeben... also z.B. auto int a oder register int b ... so in dem Kontext halt...Für einen C++0x Compiler (unterstützen praktisch alle schon) bedeutet es, dass er den Typ selbst aus dem Statement herleiten soll.
auto i = 1; // int auto str = "Hallo Welt"; //const char*etc. Ist halt vor allem bei Iteratortypen ein gewaltiger Vorteil, da man nicht die ewig langen Namen ausschreiben muss.
-
314159265358979 schrieb:
Ich habe doch nur ein & vergessen
Blöderweise aber an einer Stelle wo es der Compiler nicht bekritteln wird, und es erst zur Laufzeit kracht.
Bzw. wenn du Pech hast kracht es zur Laufzeit gar nicht, und es wird statt dessen irgendein Speicherbereich gelesen bzw. überschrieben, wo andere wichtige Daten stehen.
-
hustbaer schrieb:
314159265358979 schrieb:
Ich habe doch nur ein & vergessen
Blöderweise aber an einer Stelle wo es der Compiler nicht bekritteln wird, und es erst zur Laufzeit kracht.
Bzw. wenn du Pech hast kracht es zur Laufzeit gar nicht, und es wird statt dessen irgendein Speicherbereich gelesen bzw. überschrieben, wo andere wichtige Daten stehen.Deswegen 2x static_cast statt reinterpret_cast
-
Ich bin immer noch der Meinung, dass die static_cast Variante UB ist. Aber camper kennt sich da bestimmt besser aus. Und ja @hustbaer, da hast du natürlich Recht. Dafür eine eigene Funktion ist bei mehr als 1 Operation besser. Allerdings kann man dann gleich (zumindest bei std::vector) .data() verwenden.
-
314159265358979 schrieb:
Ich bin immer noch der Meinung, dass die static_cast Variante UB ist. Aber camper kennt sich da bestimmt besser aus. Und ja @hustbaer, da hast du natürlich Recht. Dafür eine eigene Funktion ist bei mehr als 1 Operation besser. Allerdings kann man dann gleich (zumindest bei std::vector) .data() verwenden.
Wieso sollte das UB sein?
static_cast castet beliebige Pointer von und zu void Pointern, was soll daran UB sein?
-
Ich meine irgendwann gelesen zu haben, dass lediglich ein cast von cv void* zu cv T* erlaubt ist, wenn der zuvor zu cv void* gecastete Zeiger ein Zeiger auf cv T* war.