Frage Wie serialisiert man ein Objekt in C ++?


Ich habe eine kleine Hierarchie von Objekten, die ich über eine Socket-Verbindung serialisieren und übertragen muss. Ich muss das Objekt sowohl serialisieren als auch deserialisieren, je nachdem, um welchen Typ es sich handelt. Gibt es einen einfachen Weg, dies in C ++ zu tun (wie es in Java gibt)?

Gibt es C ++ - Serialisierungs-Online-Codebeispiele oder -Tutorials?

BEARBEITEN: Um es klar zu sagen: Ich suche nach Methoden, um ein Objekt in ein Byte-Array umzuwandeln und dann zurück in ein Objekt. Ich kann mit der Steckdosenübertragung umgehen.


75
2018-02-07 14:51


Ursprung


Antworten:


Über Serialisierung sprechen, die Boost Serialisierungs-API fällt mir ein. Wie für die Übertragung der serialisierten Daten über das Netz, würde ich entweder Berkeley-Steckdosen oder die Asio-Bibliothek.

Bearbeiten:
Wenn Sie Ihre Objekte in ein Byte-Array serialisieren möchten, können Sie den Boost-Serializer wie folgt verwenden (aus der Tutorial-Site):

#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
class gps_position
{
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & degrees;
        ar & minutes;
        ar & seconds;
    }
    int degrees;
    int minutes;
    float seconds;

public:
    gps_position(){};
    gps_position(int d, int m, float s) :
    degrees(d), minutes(m), seconds(s)
    {}
};

Die eigentliche Serialisierung ist dann ziemlich einfach:

#include <fstream>
std::ofstream ofs("filename.dat", std::ios::binary);

    // create class instance
    const gps_position g(35, 59, 24.567f);

    // save data to archive
    {
        boost::archive::binary_oarchive oa(ofs);
        // write class instance to archive
        oa << g;
        // archive and stream closed when destructors are called
    }

Die Deserialisierung funktioniert in analoger Weise.

Es gibt auch Mechanismen, mit denen Sie Serialisierung von Zeigern (komplexe Datenstrukturen wie z. B. tress etc. sind kein Problem), abgeleitete Klassen und Sie können zwischen Binär-und Text-Serialisierung wählen. Außerdem werden alle STL-Container direkt aus der Box unterstützt.


50
2018-02-07 14:57



In einigen Fällen können Sie im Umgang mit einfachen Typen Folgendes tun:

object o;
socket.write(&o, sizeof(o));

Das ist in Ordnung, als Proof-of-Concept oder First-Draft, damit andere Mitglieder Ihres Teams weiter an anderen Teilen arbeiten können.

Aber früher oder später, normalerweise früher, das wird dich verletzen!

Sie stoßen auf Probleme mit:

  • Virtuelle Zeigertabellen werden beschädigt.
  • Zeiger (auf Daten / Mitglieder / Funktionen) werden beschädigt.
  • Unterschiede in der Auffüllung / Ausrichtung auf verschiedenen Computern.
  • Big / Little-Endian Bytes Reihenfolge Probleme.
  • Variationen in der Implementierung von float / double.

(Außerdem müssen Sie wissen, auf was Sie auf der Empfängerseite auspacken.)

Sie können dies verbessern, indem Sie für jede Klasse eigene Marshalling / Unmarshalling-Methoden entwickeln. (Idealerweise virtuell, so dass sie in Unterklassen erweitert werden können.) Mit ein paar einfachen Makros können Sie verschiedene Basistypen ziemlich schnell in einer großen / kleinen endian-neutralen Reihenfolge schreiben.

Aber diese Art von Grunt-Arbeit ist viel besser und leichter zu handhaben die Serialisierungsbibliothek von Boost.


13
2018-02-07 15:17



Serialisierung bedeutet, dass Sie Ihr Objekt in Binärdaten verwandeln. Während Deserialisierung bedeutet, ein Objekt aus den Daten neu zu erstellen.

Beim Serialisieren schieben Sie Bytes in ein uint8_t Vektor. Beim Deserialisieren lesen Sie Bytes von einem uint8_t Vektor.

Es gibt sicherlich Muster, die Sie beim Serialisieren von Zeug verwenden können.

Jede serialisierbare Klasse sollte eine haben serialize(std::vector<uint8_t> &binaryData) oder eine ähnliche signierte Funktion, die ihre Binärdarstellung in den bereitgestellten Vektor schreibt. Dann kann diese Funktion diesen Vektor an die Serialisierungsfunktionen seines Mitglieds übergeben, damit sie auch ihre Daten hineinschreiben können.

Da die Datendarstellung auf verschiedenen Architekturen unterschiedlich sein kann. Sie müssen ein Schema herausfinden, wie die Daten dargestellt werden.

Fangen wir mit den Grundlagen an:

Serialisierung von Ganzzahldaten

Schreiben Sie die Bytes einfach 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-Reihenfolge:

integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);

Serialisierung von Gleitkommadaten

Soweit ich weiß, hat das 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 Byte-Reihenfolge. Einige Architekturen verwenden Little Endian, andere verwenden Big Endian Byte Order. Dies bedeutet, dass Sie aufpassen müssen, welche Reihenfolge Ihnen die Bytes auf der Empfängerseite lauten. Ein weiterer Unterschied kann die Handhabung der Denormal- und Unendlich- und NAN-Werte sein. Aber 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]);
...

Die Deserialisierung macht es rückwärts. Beachten Sie die Byte-Reihenfolge Ihrer Architektur!

Serialisierung von Zeichenfolgen

Zuerst müssen Sie einer Kodierung zustimmen. UTF-8 ist üblich. Dann speichern Sie es als Länge Präfix Weise: zuerst speichern Sie die Länge der Zeichenfolge mit einer Methode, die ich oben erwähnt, dann schreibe die Zeichenfolge Byte für Byte.

Serialisierung von Arrays

Sie sind die gleichen wie eine Saite. Sie serialisieren zunächst eine Ganzzahl, die die Größe des Arrays repräsentiert, und serialisieren dann jedes Objekt darin.

Serialisierung ganzer Objekte

Wie ich schon sagte, sollten sie eine haben serialize Methode, die Inhalt zu einem Vektor hinzufügen. Um ein Objekt zu deserialisieren, sollte es einen Konstruktor haben, der Byte-Stream übernimmt. Es kann ein sein istream aber im einfachsten Fall kann es nur eine Referenz sein uint8_t Zeiger. 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 an die Konstruktoren des Felds in einer Initialisierungsliste übergeben und sie in der richtigen Reihenfolge deserialisieren lassen.

Serialisierung von Objektdiagrammen

Zuerst müssen Sie sicherstellen, dass diese Objekte wirklich etwas sind, das Sie serialisieren möchten. Sie müssen diese 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 von Zeigern, dass sie nur in dem Programm gültig sind, das sie verwendet. Sie können Zeiger nicht serialisieren, Sie sollten sie in Objekten nicht mehr verwenden. Erstellen Sie stattdessen Objektpools. Dieser Objektpool ist im Grunde ein dynamisches Array, das "Boxen" enthält. Diese Felder haben eine Referenzzählung. Nicht-Null-Referenzzählung zeigt ein Live-Objekt an, Null zeigt einen leeren Slot an. Dann erstellen Sie einen intelligenten Zeiger, der dem shared_ptr ähnlich ist, der nicht den Zeiger auf das Objekt, sondern den Index im Array speichert. Sie müssen auch einem Index zustimmen, der den Nullzeiger angibt, z. -1.

Grundsätzlich haben wir hier die Zeiger durch Array-Indizes ersetzt. Wenn Sie jetzt serialisieren, können Sie diesen Array-Index wie gewohnt serialisieren. Sie müssen sich keine Gedanken darüber machen, wo sich das Objekt im Zielsystem befindet. Stellen Sie nur sicher, dass sie den gleichen Objektpool haben.

Also müssen wir die Objektpools serialisieren. Aber welche? Nun, wenn Sie ein Objektdiagramm serialisieren, serialisieren Sie nicht nur ein Objekt, Sie serialisieren ein ganzes 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 war's. Sie sollten über eine System-Serializer-Routine verfügen, die die Serialisierung des Systems koordiniert und die relevanten Objektpools durchläuft und alle serialisiert.

Auf der Empfangsseite werden alle Arrays und die Objekte innerhalb deserialisiert, wodurch der gewünschte Objektgraph erneut erzeugt wird.

Serialisierungsfunktionszeiger

Speichern Sie keine Zeiger im Objekt. Haben Sie ein statisches Array, das die Zeiger auf diese Funktionen enthält und den Index im Objekt speichert.

Da beide Programme diese Tabelle in den Regalen kompiliert haben, sollte nur der Index funktionieren.

Serialisierung von polymorphen Typen

Da ich sagte, dass Sie Zeiger in serialisierbaren Typen vermeiden sollten und Sie stattdessen Array-Indizes verwenden sollten, kann Polymorphismus einfach nicht funktionieren, da es Zeiger benötigt.

Sie müssen dies mit Typ-Tags und Unionen umgehen.

Versionierung

Darüber hinaus. Möglicherweise möchten Sie, dass verschiedene Versionen der Software zusammenarbeiten.

In diesem Fall sollte jedes Objekt eine Versionsnummer am Anfang ihrer Serialisierung schreiben, um die Version anzuzeigen.

Wenn das Objekt auf der anderen Seite geladen wird, können neuere Objekte möglicherweise mit den älteren Darstellungen umgehen, aber die älteren können nicht mit den neueren arbeiten, so dass sie eine Ausnahme darüber werfen sollten.

Jedes Mal, wenn sich etwas ändert, sollten Sie die Versionsnummer ändern.


Um dies zu vervollständigen, kann die Serialisierung komplex sein. Aber glücklicherweise müssen Sie nicht alles in Ihrem Programm serialisieren, meistens werden nur die Protokollnachrichten serialisiert, die oft einfache alte Strukturen sind. Du brauchst also die oben genannten komplexen Tricks nicht zu oft.


0
2018-05-06 23:10