É possível serializar e desserializar uma classe em C ++?

138

É possível serializar e desserializar uma classe em C ++?

Uso Java há 3 anos e a serialização / desserialização é bastante trivial nessa linguagem. O C ++ possui recursos semelhantes? Existem bibliotecas nativas que lidam com serialização?

Um exemplo seria útil.

Agusti-N
fonte
2
não sabe o que quer dizer com "nativo", você quer dizer C ++ nativo (como Boost.Serialization)? Você quer dizer usar apenas a Biblioteca Padrão C ++? Você quer dizer outra coisa?
jwfearn
1
quero dizer "não é uma biblioteca de software externa". E desculpe, meu inglês não está muito bem: S. Eu sou da Argentina
Agusti-N
3
Não existe uma maneira nativa de serializar um objeto (você ainda pode despejar os dados binários de um POD, mas não conseguirá o que deseja). Ainda assim, o Boost, embora não seja uma "biblioteca interna", é a primeira biblioteca externa que você deve considerar adicionar ao seu compilador. Impulso é de qualidade STL (isto é, Top Gun C ++)
paercebal

Respostas:

95

A Boost::serializationbiblioteca lida com isso de maneira bastante elegante. Eu usei em vários projetos. Há um exemplo de programa, mostrando como usá-lo, aqui .

A única maneira nativa de fazer isso é usar fluxos. Isso é essencialmente tudo o que a Boost::serializationbiblioteca faz, estende o método stream configurando uma estrutura para gravar objetos em um formato semelhante a texto e lê-los no mesmo formato.

Para tipos internos, ou seus próprios tipos, com operator<<e operator>>definidos adequadamente, isso é bastante simples; consulte as Perguntas frequentes sobre C ++ para obter mais informações.

Head Geek
fonte
Parece-me que o boost :: serialization exige que o chamador acompanhe a ordem na qual os objetos são gravados e lidos. Isso está correto? Portanto, se houver uma alteração na ordem em que dois campos são gravados entre as versões de um programa, temos uma incompatibilidade. Isto está certo?
precisa saber é o seguinte
1
Provavelmente isso se deve às funções de serialização, não ao próprio código Boost :: serialization.
Chefe Geek
1
@ 0xDEADBEEF: Isso provavelmente aconteceu ao usar um arquivo binary_ (i | o), que introduz outros "problemas", como o endian-ness. Experimente o arquivo text_ (i | o), é mais independente de plataforma.
Ela782
2
Uma solução específica de estrutura / biblioteca não deve ser a resposta aceita.
Andrea Andrea
3
@ Andrea: A biblioteca Boost é um caso especial. Até a finalização do C ++ 11, era praticamente impossível escrever código C ++ moderno sem ele, por isso estava mais próximo de um STL secundário do que de uma biblioteca separada.
Chefe Geek
52

Percebo que este é um post antigo, mas é um dos primeiros que aparece ao procurar c++ serialization.

Encorajo qualquer pessoa que tenha acesso ao C ++ 11 a dar uma olhada no cereal , uma biblioteca somente de cabeçalho C ++ 11 para serialização que suporta binário, JSON e XML fora da caixa. cereal foi projetado para ser fácil de estender e usar e tem uma sintaxe semelhante ao Boost.

Azoth
fonte
4
O lado bom do cereal é que, diferentemente do impulso, ele possui metadados mínimos (quase nenhum). O boost :: serialization se torna realmente irritante quando toda vez que você abre um arquivo, ele grava sua versão da lib no fluxo, o que torna impossível anexar a um arquivo.
CyberSnoopy
@ CyberSnoopy - existe um sinalizador para suprimir esse recurso quando um arquivo é criado - é claro que você deve se lembrar dele ao ler o arquivo também.
Robert Ramey
16

O impulso é uma boa sugestão. Mas se você gostaria de fazer o seu próprio, não é tão difícil.

Basicamente, você só precisa de uma maneira de criar um gráfico de objetos e enviá-los para algum formato de armazenamento estruturado (JSON, XML, YAML, o que for). Construir o gráfico é tão simples quanto utilizar um algoritmo de marcação de objetos decentes recursivos e, em seguida, gerar todos os objetos marcados.

Escrevi um artigo descrevendo um sistema de serialização rudimentar (mas ainda poderoso). Você pode achar interessante: Usando o SQLite como um formato de arquivo em disco, parte 2 .

Frank Krueger
fonte
14

Tanto quanto "built-in" bibliotecas ir, o <<e >>foram reservadas especificamente para serialização.

Você deve substituir <<para enviar seu objeto para algum contexto de serialização (geralmente um iostream) e >>ler os dados novamente a partir desse contexto. Cada objeto é responsável pela saída de seus objetos filhos agregados.

Esse método funciona bem desde que o gráfico do objeto não contenha ciclos.

Se isso acontecer, você precisará usar uma biblioteca para lidar com esses ciclos.

Frank Krueger
fonte
3
Certamente, isso não pode estar certo ... os <<operadores implementados são usados ​​para imprimir representações de objetos legíveis por humanos, o que muitas vezes não é o que você deseja para serialização.
Einpoklum
1
@einpoklum Em vez de definir <<o genérico ostream, tente defini-lo para um fluxo de arquivos.
Carcigenicate
1
@Carcigenicate: um arquivo de log que recebe texto legível por humanos é um fluxo de arquivos.
einpoklum
1
@einpoklum Não sei bem o que você quer dizer. Frank está certo, porém, esses operadores podem ser usados ​​para serializar. Acabei de defini-los para serializar / desserializar um vetor.
Carcigenicado
2
Acho que o problema está aqui: “Você deve substituir <<o objeto em algum contexto de serialização… Cada objeto é responsável por produzir o seu…” - a questão é sobre como evitar ter que escrever isso laboriosamente para cada objeto: quanto pode ajuda de idiomas ou bibliotecas?
ShreevatsaR
14

Eu recomendo buffers de protocolo do Google . Tive a chance de testar a biblioteca de um novo projeto e é incrivelmente fácil de usar. A biblioteca é altamente otimizada para desempenho.

O Protobuf é diferente de outras soluções de serialização mencionadas aqui no sentido de que não serializa seus objetos, mas gera código para objetos que são serializados de acordo com sua especificação.

yoav.aviram
fonte
2
Você já teve experiência em serializar objetos com tamanho entre 10 e 50 MB usando isso? A documentação parece dizer que os buffers de protocolo são mais adequados para objetos com tamanho de MB.
Agnel Kurian
Eu rolei minha própria lib, ainda não usa fluxos (ainda), então é realmente para pequenas coisas: gist.github.com/earonesty/5ba3a93f391ea03ef90884840f068767
Erik Aronesty
13

O Boost :: serialization é uma ótima opção, mas encontrei um novo projeto: Cereal, que acho muito mais elegante! Eu sugiro investigar isso.

M2tM
fonte
4

Você pode verificar o protocolo amef , um exemplo de codificação C ++ no amef seria como,

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

Decodificar em java seria como,

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

A implementação do protocolo tem codecs para C ++ e Java, a parte interessante é que ele pode reter a representação da classe de objeto na forma de pares de nome e valor, eu exigi um protocolo semelhante no meu último projeto, quando eu tropecei acidentalmente nesse protocolo, eu realmente tinha modifiquei a biblioteca base de acordo com meus requisitos. Espero que isso ajude você.

Dave
fonte
3

Sweet Persist é outro.

É possível serializar de e para fluxos nos formatos XML, JSON, Lua e binário.

Vincent
fonte
Esse local parece estar em baixo, tudo o que eu poderia encontrar era este velho repo: github.com/cwbaker/sweet_persist
Janusz syf
2

Eu sugiro olhar para fábricas abstratas que são frequentemente usadas como base para serialização

Eu respondi em outra pergunta SO sobre fábricas C ++. Por favor, veja se uma fábrica flexível é de seu interesse. Eu tento descrever uma maneira antiga do ET ++ para usar macros que funcionou muito bem para mim.

O ET ++ foi um projeto para portar o MacApp antigo para C ++ e X11. No esforço disso, Eric Gamma etc começou a pensar em Design Patterns . O ET ++ continha maneiras automáticas de serialização e introspecção em tempo de execução.

epatel
fonte
0

Se você deseja um desempenho simples e melhor e não se preocupa com a compatibilidade de dados com versões anteriores, tente o HPS , é leve, muito mais rápido que o Boost, etc, e muito mais fácil de usar que o Protobuf, etc.

Exemplo:

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);
streaver91
fonte
0

Aqui está uma biblioteca serializadora simples que eu encerrei. É apenas o cabeçalho c11 e tem exemplos para serializar tipos básicos. Aqui está um para um mapa para a aula.

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

Resultado:

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
}
Neil McGill
fonte
0

Estou usando o seguinte modelo para implementar a serialização:

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

Aqui Testá o tipo que você deseja serializar Modeé um tipo fictício para diferenciar entre diferentes tipos de serialização, por exemplo. o mesmo número inteiro pode ser serializado como little endian, big endian, varint etc.

Por padrão, Serializerdelega a tarefa ao objeto que está sendo serializado. Para tipos incorporados, você deve fazer uma especialização de modelo do Serializer.

Modelos de função de conveniência também são fornecidos.

Por exemplo, pequena serialização endiana de números inteiros não assinados:

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

Em seguida, para serializar:

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

Para desserializar:

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

Devido à lógica abstrata do iterador, ele deve funcionar com qualquer iterador (por exemplo, iteradores de fluxo), ponteiro, etc.

Calmarius
fonte