Est-il possible de sérialiser et désérialiser une classe en C ++?


138

Est-il possible de sérialiser et désérialiser une classe en C ++?

J'utilise Java depuis 3 ans maintenant, et la sérialisation / désérialisation est assez triviale dans ce langage. Le C ++ a-t-il des fonctionnalités similaires? Existe-t-il des bibliothèques natives qui gèrent la sérialisation?

Un exemple serait utile.


2
pas sûr de ce que vous entendez par «natif», voulez-vous dire C ++ natif (comme Boost.Serialization)? Voulez-vous dire utiliser uniquement la bibliothèque standard C ++? Voulez-vous dire autre chose?
jwfearn

1
je veux dire "pas une bibliothèque de logiciels externe". Et désolé mon anglais n'est pas très bien: S. Je viens d'Argentine
Agusti-N

3
Il n'y a pas de moyen natif de sérialiser un objet (vous pouvez toujours vider les données binaires d'un POD, mais vous n'obtiendrez pas ce que vous voulez). Pourtant, Boost, bien que n'étant pas une "bibliothèque interne", est la première bibliothèque externe que vous devriez envisager d'ajouter à votre compilateur. Boost est de qualité STL (ie Top Gun C ++)
Paercebal

Réponses:


95

La Boost::serializationbibliothèque gère cela assez élégamment. Je l'ai utilisé dans plusieurs projets. Il y a un exemple de programme, montrant comment l'utiliser, ici .

La seule façon native de le faire est d'utiliser des flux. C'est essentiellement tout ce que fait la Boost::serializationbibliothèque, elle étend la méthode de flux en configurant un cadre pour écrire des objets dans un format de type texte et les lire à partir du même format.

Pour les types intégrés, ou vos propres types avec operator<<et operator>>correctement définis, c'est assez simple; consultez la FAQ C ++ pour plus d'informations.


Il me semble que boost :: serialization nécessite que l'appelant garde une trace de l'ordre dans lequel les objets sont écrits et lus. Est-ce exact? Donc, s'il y a un changement dans l'ordre dans lequel deux champs sont écrits entre les versions d'un programme, alors nous avons une incompatibilité. Est-ce correct?
Agnel Kurian

1
Cela serait probablement dû aux fonctions de sérialisation, pas au code de sérialisation Boost :: lui-même.
Head Geek

1
@ 0xDEADBEEF: Cela s'est probablement produit lors de l'utilisation d'une archive binary_ (i | o), qui introduit d'autres "problèmes" comme endian-ness. Essayez l'archive text_ (i | o), elle est plus indépendante de la plate-forme.
Ela782 du

2
Une solution cadre / bibliothèque spécifique ne devrait pas être la réponse acceptée.
Andrea

3
@Andrea: La bibliothèque Boost est un cas particulier. Jusqu'à ce que C ++ 11 soit finalisé, il était pratiquement impossible d'écrire du code C ++ moderne sans lui, donc c'était plus proche d'une STL secondaire qu'une bibliothèque séparée.
Head Geek

52

Je me rends compte que c'est un ancien post, mais c'est l'un des premiers qui apparaît lors de la recherche c++ serialization.

J'encourage tous ceux qui ont accès à C ++ 11 à jeter un œil à cereal , une bibliothèque d'en-tête C ++ 11 uniquement pour la sérialisation qui prend en charge binaire, JSON et XML prêt à l'emploi. cereal a été conçu pour être facile à étendre et à utiliser et a une syntaxe similaire à Boost.


4
La bonne chose à propos des céréales est que contrairement à boost, elles ont des métadonnées minimales (presque aucune). boost :: serialization devient vraiment ennuyeux quand chaque fois que vous ouvrez une archive, il écrit sa version lib dans le flux, ce qui rend l'ajout à un fichier impossible.
CyberSnoopy

@CyberSnoopy - il existe un indicateur pour supprimer cette fonctionnalité lorsqu'une archive est créée - bien sûr, vous devez également vous en souvenir lors de la lecture de l'archive.
Robert Ramey

16

Boost est une bonne suggestion. Mais si vous souhaitez rouler vous-même, ce n'est pas si difficile.

Fondamentalement, vous avez juste besoin d'un moyen de créer un graphique d'objets, puis de les exporter dans un format de stockage structuré (JSON, XML, YAML, peu importe). Construire le graphique est aussi simple que d'utiliser un algorithme d'objet décent récursif de marquage, puis de sortir tous les objets marqués.

J'ai écrit un article décrivant un système de sérialisation rudimentaire (mais toujours puissant). Vous pouvez trouver cela intéressant: Utilisation de SQLite comme format de fichier sur disque, partie 2 .


14

En ce qui concerne les bibliothèques "intégrées", les <<et >>ont été réservés spécifiquement pour la sérialisation.

Vous devez remplacer <<pour afficher votre objet dans un contexte de sérialisation (généralement un iostream) et >>pour lire les données à partir de ce contexte. Chaque objet est responsable de la sortie de ses objets enfants agrégés.

Cette méthode fonctionne correctement tant que votre graphique d'objets ne contient aucun cycle.

Si c'est le cas, vous devrez utiliser une bibliothèque pour gérer ces cycles.


3
Sûrement, cela ne peut pas être juste ... les <<opérateurs implémentés sont utilisés pour imprimer des représentations textuelles d'objets lisibles par l'homme, ce qui n'est très souvent pas ce que vous voulez pour la sérialisation.
einpoklum

1
@einpoklum Au lieu de définir <<pour le générique ostream, essayez de le définir pour un flux de fichiers.
Carcigenicate

1
@Carcigenicate: Un fichier journal qui prend du texte lisible par l'homme est un flux de fichiers.
einpoklum

1
@einpoklum Je ne sais pas trop ce que vous voulez dire. Frank a raison cependant, ces opérateurs peuvent être utilisés pour sérialiser. Je viens de les définir pour sérialiser / désérialiser un vecteur.
Carcigenicate

2
Je pense que le piège est là: «Vous devriez remplacer <<pour sortir votre objet dans un contexte de sérialisation… Chaque objet est responsable de la sortie de son…» - la question est de savoir comment éviter d'avoir à écrire cela laborieusement pour chaque objet: à quel point le aide à la langue ou aux bibliothèques?
ShreevatsaR

14

Je recommande les tampons de protocole Google . J'ai eu la chance de tester la bibliothèque sur un nouveau projet et c'est remarquablement facile à utiliser. La bibliothèque est fortement optimisée pour les performances.

Protobuf est différent des autres solutions de sérialisation mentionnées ici dans le sens où il ne sérialise pas vos objets, mais génère plutôt du code pour les objets qui sont sérialisés selon vos spécifications.


2
Avez-vous déjà eu l'occasion de sérialiser des objets d'une taille d'environ 10 à 50 Mo avec cela? La documentation semble indiquer que les tampons de protocole sont les mieux adaptés aux objets d'une taille d'environ un Mo.
Agnel Kurian

J'ai roulé ma propre bibliothèque, n'utilise pas (encore) de flux, donc c'est vraiment pour de petites choses: gist.github.com/earonesty/5ba3a93f391ea03ef90884840f068767
Erik Aronesty


4

Vous pouvez vérifier le protocole amef , un exemple d'encodage C ++ dans amef serait comme,

    //Create a new AMEF object
    AMEFObject *object = new AMEFObject();

    //Add a child string object
    object->addPacket("This is the Automated Message Exchange Format Object property!!","adasd");   

    //Add a child integer object
    object->addPacket(21213);

    //Add a child boolean object
    object->addPacket(true);

    AMEFObject *object2 = new AMEFObject();
    string j = "This is the property of a nested Automated Message Exchange Format Object";
    object2->addPacket(j);
    object2->addPacket(134123);
    object2->addPacket(false);

    //Add a child character object
    object2->addPacket('d');

    //Add a child AMEF Object
    object->addPacket(object2);

    //Encode the AMEF obejct
    string str = new AMEFEncoder()->encode(object,false);

Le décodage en java serait comme,

    string arr = amef encoded byte array value;
    AMEFDecoder decoder = new AMEFDecoder()
    AMEFObject object1 = AMEFDecoder.decode(arr,true);

L'implémentation du protocole a des codecs pour C ++ et Java, la partie intéressante est qu'elle peut conserver la représentation de la classe d'objet sous la forme de paires de valeurs de nom, j'avais besoin d'un protocole similaire dans mon dernier projet, quand je suis tombé par hasard sur ce protocole, j'avais en fait modifié la bibliothèque de base selon mes besoins. J'espère que cela vous aide.




2

Je suggère d'examiner les usines abstraites qui sont souvent utilisées comme base pour la sérialisation

J'ai répondu à une autre question SO sur les usines C ++. Veuillez y voir si une usine flexible présente un intérêt. J'essaie de décrire une ancienne méthode d'ET ++ pour utiliser des macros qui a très bien fonctionné pour moi.

ET ++ était un projet de portage de l'ancien MacApp vers C ++ et X11. Dans cet effort, Eric Gamma, etc. a commencé à réfléchir aux modèles de conception . ET ++ contenait des moyens automatiques de sérialisation et d'introspection lors de l'exécution.


0

Si vous voulez des performances simples et optimales et que vous ne vous souciez pas de la compatibilité ascendante des données, essayez HPS , il est léger, beaucoup plus rapide que Boost, etc., et beaucoup plus facile à utiliser que Protobuf, etc.

Exemple:

std::vector<int> data({22, 333, -4444});
std::string serialized = hps::serialize_to_string(data);
auto parsed = hps::parse_from_string<std::vector<int>>(serialized);

0

Voici une bibliothèque de sérialiseur simple que j'ai créée. C'est l'en-tête uniquement, c11 et contient des exemples pour la sérialisation des types de base. En voici une pour une carte en classe.

https://github.com/goblinhack/simple-c-plus-plus-serializer

#include "c_plus_plus_serializer.h"

class Custom {
public:
    int a;
    std::string b;
    std::vector c;

    friend std::ostream& operator<<(std::ostream &out, 
                                    Bits my)
    {
        out << bits(my.t.a) << bits(my.t.b) << bits(my.t.c);
        return (out);
    }

    friend std::istream& operator>>(std::istream &in, 
                                    Bits my)
    {
        in >> bits(my.t.a) >> bits(my.t.b) >> bits(my.t.c);
        return (in);
    }

    friend std::ostream& operator<<(std::ostream &out, 
                                    class Custom &my)
    {
        out << "a:" << my.a << " b:" << my.b;

        out << " c:[" << my.c.size() << " elems]:";
        for (auto v : my.c) {
            out << v << " ";
        }
        out << std::endl;

        return (out);
    }
};

static void save_map_key_string_value_custom (const std::string filename)
{
    std::cout << "save to " << filename << std::endl;
    std::ofstream out(filename, std::ios::binary );

    std::map< std::string, class Custom > m;

    auto c1 = Custom();
    c1.a = 1;
    c1.b = "hello";
    std::initializer_list L1 = {"vec-elem1", "vec-elem2"};
    std::vector l1(L1);
    c1.c = l1;

    auto c2 = Custom();
    c2.a = 2;
    c2.b = "there";
    std::initializer_list L2 = {"vec-elem3", "vec-elem4"};
    std::vector l2(L2);
    c2.c = l2;

    m.insert(std::make_pair(std::string("key1"), c1));
    m.insert(std::make_pair(std::string("key2"), c2));

    out << bits(m);
}

static void load_map_key_string_value_custom (const std::string filename)
{
    std::cout << "read from " << filename << std::endl;
    std::ifstream in(filename);

    std::map< std::string, class Custom > m;

    in >> bits(m);
    std::cout << std::endl;

    std::cout << "m = " << m.size() << " list-elems { " << std::endl;
    for (auto i : m) {
        std::cout << "    [" << i.first << "] = " << i.second;
    }
    std::cout << "}" << std::endl;
}

void map_custom_class_example (void)
{
    std::cout << "map key string, value class" << std::endl;
    std::cout << "============================" << std::endl;
    save_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
    load_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
    std::cout << std::endl;
}

Production:

map key string, value class
============================
save to map_of_custom_class.bin
read from map_of_custom_class.bin

m = 2 list-elems {
    [key1] = a:1 b:hello c:[2 elems]:vec-elem1 vec-elem2
    [key2] = a:2 b:there c:[2 elems]:vec-elem3 vec-elem4
}

0

J'utilise le modèle suivant pour implémenter la sérialisation:

template <class T, class Mode = void> struct Serializer
{
    template <class OutputCharIterator>
    static void serializeImpl(const T &object, OutputCharIterator &&it)
    {
        object.template serializeThis<Mode>(it);
    }

    template <class InputCharIterator>
    static T deserializeImpl(InputCharIterator &&it, InputCharIterator &&end)
    {
        return T::template deserializeFrom<Mode>(it, end);
    }
};

template <class Mode = void, class T, class OutputCharIterator>
void serialize(const T &object, OutputCharIterator &&it)
{
    Serializer<T, Mode>::serializeImpl(object, it);
}

template <class T, class Mode = void, class InputCharIterator>
T deserialize(InputCharIterator &&it, InputCharIterator &&end)
{
    return Serializer<T, Mode>::deserializeImpl(it, end);
}

template <class Mode = void, class T, class InputCharIterator>
void deserialize(T &result, InputCharIterator &&it, InputCharIterator &&end)
{
    result = Serializer<T, Mode>::deserializeImpl(it, end);
}

Voici Tle type que vous souhaitez sérialiser Modeest un type factice pour différencier les différents types de sérialisation, par exemple. le même entier peut être sérialisé en tant que little endian, big endian, varint, etc.

Par défaut, Serializerdélègue la tâche à l'objet en cours de sérialisation. Pour les types intégrés, vous devez créer une spécialisation de modèle du Serializer.

Des modèles de fonctions pratiques sont également fournis.

Par exemple, la sérialisation little endian d'entiers non signés:

struct LittleEndianMode
{
};

template <class T>
struct Serializer<
    T, std::enable_if_t<std::is_unsigned<T>::value, LittleEndianMode>>
{
    template <class InputCharIterator>
    static T deserializeImpl(InputCharIterator &&it, InputCharIterator &&end)
    {
        T res = 0;

        for (size_t i = 0; i < sizeof(T); i++)
        {
            if (it == end) break;
            res |= static_cast<T>(*it) << (CHAR_BIT * i);
            it++;
        }

        return res;
    }

    template <class OutputCharIterator>
    static void serializeImpl(T number, OutputCharIterator &&it)
    {
        for (size_t i = 0; i < sizeof(T); i++)
        {
            *it = (number >> (CHAR_BIT * i)) & 0xFF;
            it++;
        }
    }
};

Puis pour sérialiser:

std::vector<char> serialized;
uint32_t val = 42;
serialize<LittleEndianMode>(val, std::back_inserter(serialized));

Pour désérialiser:

uint32_t val;
deserialize(val, serialized.begin(), serialized.end());

En raison de la logique de l'itérateur abstrait, il devrait fonctionner avec n'importe quel itérateur (par exemple, les itérateurs de flux), pointeur, etc.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.