Es gibt ein generisches Muster, mit dem Sie Objekte serialisieren können. Das grundlegende Grundelement sind diese beiden Funktionen, die Sie von Iteratoren lesen und schreiben können:
template <class OutputCharIterator>
void putByte(char byte, OutputCharIterator &&it)
{
*it = byte;
++it;
}
template <class InputCharIterator>
char getByte(InputCharIterator &&it, InputCharIterator &&end)
{
if (it == end)
{
throw std::runtime_error{"Unexpected end of stream."};
}
char byte = *it;
++it;
return byte;
}
Dann folgen Serialisierungs- und Deserialisierungsfunktionen dem Muster:
template <class OutputCharIterator>
void serialize(const YourType &obj, OutputCharIterator &&it)
{
// Call putbyte or other serialize overloads.
}
template <class InputCharIterator>
void deserialize(YourType &obj, InputCharIterator &&it, InputCharIterator &&end)
{
// Call getByte or other deserialize overloads.
}
Für Klassen können Sie das Friend-Funktionsmuster verwenden, damit die Überladung mithilfe von ADL gefunden werden kann:
class Foo
{
int internal1, internal2;
// So it can be found using ADL and it accesses private parts.
template <class OutputCharIterator>
friend void serialize(const Foo &obj, OutputCharIterator &&it)
{
// Call putByte or other serialize overloads.
}
// Deserialize similar.
};
Die in Ihrem Programm können Sie serialisieren und in eine Datei wie diese objektivieren:
std::ofstream file("savestate.bin");
serialize(yourObject, std::ostreambuf_iterator<char>(file));
Dann lies:
std::ifstream file("savestate.bin");
deserialize(yourObject, std::istreamBuf_iterator<char>(file), std::istreamBuf_iterator<char>());
Meine alte Antwort hier:
Serialisierung bedeutet, dass Sie Ihr Objekt in Binärdaten umwandeln. Während Deserialisierung bedeutet, ein Objekt aus den Daten neu zu erstellen.
Bei der Serialisierung werden Bytes in einen uint8_t
Vektor verschoben. Beim Unserialisieren lesen Sie Bytes aus einem uint8_t
Vektor.
Es gibt sicherlich Muster, die Sie beim Serialisieren von Inhalten verwenden können.
Jede serialisierbare Klasse sollte eine serialize(std::vector<uint8_t> &binaryData)
oder eine ähnliche signierte Funktion haben, die ihre binäre Darstellung in den bereitgestellten Vektor schreibt. Dann kann diese Funktion diesen Vektor an die Serialisierungsfunktionen des Mitglieds weitergeben, damit diese auch ihre Daten darin schreiben können.
Da die Datendarstellung auf verschiedenen Architekturen unterschiedlich sein kann. Sie müssen ein Schema finden, wie die Daten dargestellt werden.
Beginnen wir mit den Grundlagen:
Ganzzahlige Daten serialisieren
Schreiben Sie einfach die Bytes in Little-Endian-Reihenfolge. Oder verwenden Sie die Varint-Darstellung, wenn es auf die Größe ankommt.
Serialisierung in Little-Endian-Reihenfolge:
data.push_back(integer32 & 0xFF);
data.push_back((integer32 >> 8) & 0xFF);
data.push_back((integer32 >> 16) & 0xFF);
data.push_back((integer32 >> 24) & 0xFF);
Deserialisierung aus Little Endian Order:
integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
Gleitkommadaten serialisieren
Soweit ich weiß, hat der IEEE 754 hier ein Monopol. Ich kenne keine Mainstream-Architektur, die etwas anderes für Floats verwenden würde. Das einzige, was anders sein kann, ist die Bytereihenfolge. Einige Architekturen verwenden Little Endian, andere die Big Endian-Bytereihenfolge. Dies bedeutet, dass Sie vorsichtig sein müssen, in welcher Reihenfolge die Bytes auf der Empfangsseite laut werden. Ein weiterer Unterschied kann die Handhabung der Denormal- und Unendlichkeits- und NAN-Werte sein. Solange Sie diese Werte vermeiden, sollten Sie in Ordnung sein.
Serialisierung:
uint8_t mem[8];
memcpy(mem, doubleValue, 8);
data.push_back(mem[0]);
data.push_back(mem[1]);
...
Deserialisierung macht es rückwärts. Beachten Sie die Bytereihenfolge Ihrer Architektur!
Strings serialisieren
Zuerst müssen Sie sich auf eine Kodierung einigen. UTF-8 ist üblich. Speichern Sie es dann als Länge vorangestellt: Zuerst speichern Sie die Länge der Zeichenfolge mit einer oben erwähnten Methode, dann schreiben Sie die Zeichenfolge Byte für Byte.
Arrays serialisieren.
Sie sind die gleichen wie eine Saite. Sie serialisieren zuerst eine Ganzzahl, die die Größe des Arrays darstellt, und serialisieren dann jedes Objekt darin.
Ganze Objekte serialisieren
Wie ich bereits sagte, sollten sie eine serialize
Methode haben, die einem Vektor Inhalt hinzufügt. Um ein Objekt unserialisieren zu können, sollte es einen Konstruktor haben, der Byte-Streams verwendet. Es kann istream
ein Referenzzeiger sein, aber im einfachsten Fall kann es nur ein Referenzzeiger sein uint8_t
. Der Konstruktor liest die gewünschten Bytes aus dem Stream und richtet die Felder im Objekt ein. Wenn das System gut entworfen ist und die Felder in der Reihenfolge der Objektfelder serialisiert, können Sie den Stream einfach in einer Initialisierungsliste an die Konstruktoren des Felds übergeben und sie in der richtigen Reihenfolge deserialisieren lassen.
Objektdiagramme serialisieren
Zuerst müssen Sie sicherstellen, dass diese Objekte wirklich etwas sind, das Sie serialisieren möchten. Sie müssen sie nicht serialisieren, wenn Instanzen dieser Objekte auf dem Ziel vorhanden sind.
Jetzt haben Sie herausgefunden, dass Sie das Objekt, auf das ein Zeiger zeigt, serialisieren müssen. Das Problem der Zeiger, dass sie nur in dem Programm gültig sind, das sie verwendet. Sie können Zeiger nicht serialisieren, Sie sollten sie nicht mehr in Objekten verwenden. Erstellen Sie stattdessen Objektpools. Dieser Objektpool ist im Grunde ein dynamisches Array, das "Boxen" enthält. Diese Felder haben eine Referenzanzahl. Ein Referenzzähler ungleich Null zeigt ein lebendes Objekt an, Null zeigt einen leeren Steckplatz an. Anschließend erstellen Sie einen intelligenten Zeiger, der dem shared_ptr ähnelt und nicht den Zeiger auf das Objekt, sondern den Index im Array speichert. Sie müssen sich auch auf einen Index einigen, der den Nullzeiger kennzeichnet, z. -1.
Grundsätzlich haben wir hier die Zeiger durch Array-Indizes ersetzt. Jetzt können Sie beim Serialisieren diesen Array-Index wie gewohnt serialisieren. Sie müssen sich keine Gedanken darüber machen, wo sich das Objekt auf dem Zielsystem im Speicher befindet. Stellen Sie einfach sicher, dass sie auch denselben Objektpool haben.
Wir müssen also die Objektpools serialisieren. Aber welche? Wenn Sie ein Objektdiagramm serialisieren, serialisieren Sie nicht nur ein Objekt, sondern ein gesamtes System. Dies bedeutet, dass die Serialisierung des Systems nicht von Teilen des Systems ausgehen sollte. Diese Objekte sollten sich nicht um den Rest des Systems kümmern, sie müssen nur die Array-Indizes serialisieren und das wars. Sie sollten über eine System-Serializer-Routine verfügen, die die Serialisierung des Systems koordiniert, die relevanten Objektpools durchläuft und alle serialisiert.
Auf der Empfangsseite werden alle Arrays und die darin enthaltenen Objekte deserialisiert, wodurch der gewünschte Objektgraph neu erstellt wird.
Funktionszeiger serialisieren
Speichern Sie keine Zeiger im Objekt. Haben Sie ein statisches Array, das die Zeiger auf diese Funktionen enthält, und speichern Sie den Index im Objekt.
Da diese Programme in beiden Programmen in die Regale kompiliert sind, sollte es funktionieren, nur den Index zu verwenden.
Serialisierung polymorpher Typen
Da ich sagte, Sie sollten Zeiger in serialisierbaren Typen vermeiden und stattdessen Array-Indizes verwenden, kann Polymorphismus einfach nicht funktionieren, da Zeiger erforderlich sind.
Sie müssen dies mit Typ-Tags und Gewerkschaften umgehen.
Versionierung
Darüber hinaus. Möglicherweise möchten Sie, dass verschiedene Versionen der Software zusammenarbeiten.
In diesem Fall sollte jedes Objekt zu Beginn seiner Serialisierung eine Versionsnummer schreiben, um die Version anzugeben.
Wenn Sie das Objekt auf der anderen Seite laden, können neuere Objekte möglicherweise die älteren Darstellungen verarbeiten, aber die älteren können die neueren nicht verarbeiten, sodass sie eine Ausnahme auslösen sollten.
Jedes Mal, wenn sich etwas ändert, sollten Sie die Versionsnummer erhöhen.
Zum Abschluss kann die Serialisierung komplex sein. Glücklicherweise müssen Sie nicht alles in Ihrem Programm serialisieren. Meistens werden nur die Protokollnachrichten serialisiert, bei denen es sich häufig um einfache alte Strukturen handelt. Sie brauchen also die komplexen Tricks, die ich oben erwähnt habe, nicht zu oft.