È possibile serializzare e deserializzare una classe in C ++?


138

È possibile serializzare e deserializzare una classe in C ++?

Uso Java da 3 anni e la serializzazione / deserializzazione è abbastanza banale in quella lingua. Il C ++ ha funzionalità simili? Esistono librerie native che gestiscono la serializzazione?

Un esempio sarebbe utile.


2
non sei sicuro di cosa intendi per "nativo", vuoi dire C ++ nativo (come Boost.Serialization)? Intendi usare solo la libreria standard C ++? Intendi qualcos'altro?
jwfearn,

1
intendo "non una libreria software esterna". E scusami se il mio inglese non sta molto bene: S. Vengo dall'Argentina
Agusti-N,

3
Non esiste un modo nativo per serializzare un oggetto (è ancora possibile scaricare i dati binari da un POD, ma non si otterrà ciò che si desidera). Tuttavia, Boost, sebbene non sia una "libreria interna", è la prima libreria esterna che dovresti considerare di aggiungere al tuo compilatore. Boost è di qualità STL (ovvero Top Gun C ++)
paercebal,

Risposte:


95

La Boost::serializationbiblioteca lo gestisce piuttosto elegantemente. L'ho usato in diversi progetti. C'è un programma di esempio, che mostra come usarlo, qui .

L'unico modo nativo per farlo è utilizzare i flussi. Questo è essenzialmente tutto ciò che fa la Boost::serializationlibreria, estende il metodo stream impostando un framework per scrivere oggetti in un formato simile a testo e leggerli dallo stesso formato.

Per i tipi incorporati, o i tuoi tipi con operator<<e operator>>correttamente definiti, è abbastanza semplice; vedere le domande frequenti su C ++ per ulteriori informazioni.


Mi sembra che boost :: serialization richieda al chiamante di tenere traccia dell'ordine in cui gli oggetti vengono scritti e letti. È corretto? Quindi se c'è un cambiamento nell'ordine in cui due campi sono scritti tra le versioni di un programma, allora abbiamo un'incompatibilità. È giusto?
Agnel Kurian,

1
Ciò sarebbe probabilmente dovuto alle funzioni di serializzazione, non al codice Boost :: serialization stesso.
Head Geek,

1
@ 0xDEADBEEF: Questo probabilmente accade quando si utilizza un archivio binary_ (i | o), che introduce altri "problemi" come l'endian-ness. Prova l'archivio text_ (i | o), è più indipendente dalla piattaforma.
Ela782,

2
Una soluzione specifica di framwork / libreria non dovrebbe essere la risposta accettata.
Andrea

3
@Andrea: la libreria Boost è un caso speciale. Fino alla finalizzazione di C ++ 11, era quasi impossibile scrivere il moderno codice C ++ senza di esso, quindi era più vicino a un STL secondario che a una libreria separata.
Head Geek

52

Mi rendo conto che questo è un vecchio post, ma è uno dei primi che viene fuori durante la ricerca c++ serialization.

Incoraggio chiunque abbia accesso a C ++ 11 a dare un'occhiata ai cereali , una libreria di sola intestazione C ++ 11 per la serializzazione che supporta binario, JSON e XML. cereal è stato progettato per essere facile da estendere e utilizzare e ha una sintassi simile a Boost.


4
La cosa buona del cereale è che a differenza della spinta, ha metadati minimi (quasi nessuno). boost :: la serializzazione diventa davvero fastidiosa quando ogni volta che apri un archivio, scrive la sua versione lib sullo stream, il che rende impossibile aggiungere un file.
CyberSnoopy,

@CyberSnoopy - c'è una bandiera per sopprimere questa funzione quando viene creato un archivio - ovviamente devi ricordarlo anche quando leggi l'archivio.
Robert Ramey,

16

Boost è un buon suggerimento. Ma se vuoi farti il ​​tuo, non è così difficile.

Fondamentalmente hai solo bisogno di un modo per costruire un grafico di oggetti e poi inviarli in un formato di archiviazione strutturato (JSON, XML, YAML, qualunque cosa). Costruire il grafico è semplice come utilizzare un algoritmo di oggetti decenti ricorsivi di marcatura e quindi emettere tutti gli oggetti marcati.

Ho scritto un articolo che descrive un rudimentale (ma comunque potente) sistema di serializzazione. Potresti trovarlo interessante: usare SQLite come formato di file su disco, parte 2 .


14

Per quanto riguarda le librerie "integrate", il <<e>> sono state riservate specificamente per la serializzazione.

Dovresti sovrascrivere <<l'output dell'oggetto in un contesto di serializzazione (di solito un iostream) e >>rileggere i dati da quel contesto. Ogni oggetto è responsabile della produzione dei suoi oggetti figlio aggregati.

Questo metodo funziona bene fino a quando il grafico dell'oggetto non contiene cicli.

In tal caso, dovrai utilizzare una libreria per gestire quei cicli.


3
Sicuramente, non può essere giusto ... gli <<operatori implementati vengono utilizzati per stampare rappresentazioni testuali di oggetti leggibili dall'uomo, che spesso non è ciò che si desidera per la serializzazione.
einpoklum,

1
@einpoklum Invece di definire <<per il generico ostream, prova a definirlo per un flusso di file.
Carcigenicato

1
@Carcigenicato: un file di registro che accetta testo leggibile dall'uomo è un flusso di file.
einpoklum,

1
@einpoklum Non sono sicuro di cosa intendi. Frank ha ragione, tuttavia, quegli operatori possono essere usati per serializzare. Li ho appena definiti per serializzare / deserializzare un vettore.
Carcigenicato

2
Penso che il problema sia qui "Dovresti sovrascrivere <<l'output del tuo oggetto in un contesto di serializzazione ... Ogni oggetto è responsabile della produzione del suo ..." - la domanda è su come evitare di doverlo scrivere faticosamente per ogni oggetto: quanto può lingua o biblioteche aiutano?
ShreevatsaR,

14

Consiglio i buffer di protocollo di Google . Ho avuto la possibilità di testare la libreria su un nuovo progetto ed è straordinariamente facile da usare. La libreria è fortemente ottimizzata per le prestazioni.

Protobuf è diverso dalle altre soluzioni di serializzazione qui menzionate, nel senso che non serializza i tuoi oggetti, ma piuttosto genera codice per oggetti che sono serializzati secondo le tue specifiche.


2
Hai avuto esperienza nella serializzazione di oggetti con dimensioni di circa 10-50 MB usando questo? La documentazione sembra dire che i buffer di protocollo sono più adatti per oggetti di dimensioni pari a un MB.
Agnel Kurian,

Ho lanciato la mia lib, non uso i flussi (ancora), quindi è davvero per piccole cose: gist.github.com/earonesty/5ba3a93f391ea03ef90884840f068767
Erik Aronesty


4

Puoi controllare il protocollo amef , un esempio di codifica C ++ in amef sarebbe come,

    //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);

La decodifica in Java sarebbe come,

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

L'implementazione del Protocollo ha codec sia per C ++ che per Java, la parte interessante è che può conservare la rappresentazione della classe di oggetti sotto forma di coppie nome-valore, ho richiesto un protocollo simile nel mio ultimo progetto, quando per caso mi sono imbattuto in questo protocollo, in realtà modificato la libreria di base in base alle mie esigenze. Spero che questo ti aiuti.




2

Suggerisco di esaminare le fabbriche astratte che vengono spesso utilizzate come base per la serializzazione

Ho risposto in un'altra domanda SO sulle fabbriche C ++. Si prega di vedere se una fabbrica flessibile è di interesse. Provo a descrivere un vecchio modo da ET ++ per usare le macro che ha funzionato alla grande per me.

ET ++ era un progetto per il porting della vecchia MacApp su C ++ e X11. Nello sforzo di esso Eric Gamma ecc ha iniziato a pensare a Design Patterns . ET ++ conteneva modi automatici di serializzazione e introspezione in fase di esecuzione.


0

Se vuoi prestazioni semplici e migliori e non ti preoccupi della compatibilità dei dati con le versioni precedenti, prova HPS , è leggero, molto più veloce di Boost, ecc. E molto più facile da usare rispetto a Protobuf, ecc.

Esempio:

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

Ecco una semplice libreria di serializzatori che ho rovesciato. È solo intestazione, c11 e ha esempi per serializzare i tipi di base. Eccone uno per una mappa per 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;
}

Produzione:

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

Sto usando il seguente modello per implementare la serializzazione:

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);
}

Ecco Til tipo che si desidera serializzare Modeè un tipo fittizio per distinguere tra diversi tipi di serializzazione, ad es. lo stesso numero intero può essere serializzato come little endian, big endian, varint, ecc.

Per impostazione predefinita, Serializerdelega l'attività all'oggetto in fase di serializzazione. Per i tipi predefiniti, è necessario creare una specializzazione modello di Serializer.

Sono inoltre disponibili modelli di funzioni utili.

Ad esempio serializzazione little endian di numeri interi senza segno:

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++;
        }
    }
};

Quindi serializzare:

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

Per deserializzare:

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

A causa della logica astratta dell'iteratore, dovrebbe funzionare con qualsiasi iteratore (es. Stream iteratori), puntatore, ecc.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.