¿Es posible serializar y deserializar una clase en C ++?

138

¿Es posible serializar y deserializar una clase en C ++?

He estado usando Java durante 3 años, y la serialización / deserialización es bastante trivial en ese lenguaje. ¿C ++ tiene características similares? ¿Hay bibliotecas nativas que manejan la serialización?

Un ejemplo sería útil.

Agusti-N
fuente
2
no estoy seguro de lo que quieres decir con "nativo", ¿te refieres a C ++ nativo (como Boost.Serialization)? ¿Te refieres a usar solo la biblioteca estándar de C ++? ¿Te refieres a algo más?
jwfearn
1
Me refiero a "no una biblioteca de software externa". Y lo siento, mi inglés no está muy bien: S. Soy de Argentina
Agusti-N
3
No existe una forma nativa de serializar un objeto (aún puede volcar los datos binarios de un POD, pero no obtendrá lo que desea). Aún así, Boost, aunque no es una "biblioteca interna", es la primera biblioteca externa que debe considerar agregar a su compilador. Boost es de calidad STL (es decir, Top Gun C ++)
paercebal

Respuestas:

95

La Boost::serializationbiblioteca maneja esto con bastante elegancia. Lo he usado en varios proyectos. Hay un programa de ejemplo, que muestra cómo usarlo, aquí .

La única forma nativa de hacerlo es usar secuencias. Eso es esencialmente todo lo Boost::serializationque hace la biblioteca, extiende el método de flujo configurando un marco para escribir objetos en un formato similar al texto y leerlos desde el mismo formato.

Para los tipos integrados, o sus propios tipos con operator<<y operator>>adecuadamente definidos, eso es bastante simple; Consulte las preguntas frecuentes de C ++ para obtener más información.

Head Geek
fuente
Me parece que boost :: serialization requiere que la persona que llama realice un seguimiento del orden en que se escriben y leen los objetos. ¿Es eso correcto? Entonces, si hay un cambio en el orden en que se escriben dos campos entre las versiones de un programa, entonces tenemos una incompatibilidad. ¿Es esto correcto?
Agnel Kurian
1
Eso probablemente se deba a las funciones de serialización, no al código Boost :: serialización en sí.
Head Geek
1
@ 0xDEADBEEF: Eso probablemente sucedió cuando se usa un archivo binario_ (i | o), que introduce otros "problemas" como endian-ness. Pruebe el archivo text_ (i | o), es más independiente de la plataforma.
Ela782
2
Una solución específica de framwork / biblioteca no debe ser la respuesta aceptada.
Andrea
3
@Andrea: La biblioteca Boost es un caso especial. Hasta que se finalizó C ++ 11, era casi imposible escribir código C ++ moderno sin él, por lo que estaba más cerca de un STL secundario que de una biblioteca separada.
Head Geek
52

Me doy cuenta de que esta es una publicación antigua, pero es una de las primeras que aparece al buscar c++ serialization.

Recomiendo a cualquiera que tenga acceso a C ++ 11 que eche un vistazo a cereal , una biblioteca de encabezado de C ++ 11 solo para serialización que admite binarios, JSON y XML listos para usar. cereal fue diseñado para ser fácil de extender y usar y tiene una sintaxis similar a Boost.

Azoth
fuente
44
Lo bueno del cereal es que, a diferencia del impulso, tiene metadatos mínimos (casi ninguno). boost :: serialization se vuelve realmente molesto cuando cada vez que abre un archivo, escribe su versión lib en la transmisión, lo que hace que sea imposible agregar a un archivo.
CyberSnoopy
@CyberSnoopy: hay una marca para suprimir esta función cuando se crea un archivo, por supuesto, también debe recordarlo al leer el archivo.
Robert Ramey
16

Boost es una buena sugerencia. Pero si desea rodar el suyo, no es tan difícil.

Básicamente, solo necesita una forma de construir un gráfico de objetos y luego enviarlos a un formato de almacenamiento estructurado (JSON, XML, YAML, lo que sea). Construir el gráfico es tan simple como utilizar un algoritmo recursivo de marcado de objetos decentes y luego generar todos los objetos marcados.

Escribí un artículo que describe un sistema de serialización rudimentario (pero aún poderoso). Puede que le resulte interesante: Uso de SQLite como formato de archivo en disco, Parte 2 .

Frank Krueger
fuente
14

En cuanto a las bibliotecas "integradas", las <<y >>se han reservado específicamente para la serialización.

Debe anular la <<salida de su objeto a algún contexto de serialización (generalmente un iostream) y >>leer los datos desde ese contexto. Cada objeto es responsable de generar sus objetos secundarios agregados.

Este método funciona bien siempre que su gráfico de objeto no contenga ciclos.

Si es así, tendrá que usar una biblioteca para manejar esos ciclos.

Frank Krueger
fuente
3
Seguramente, eso no puede ser correcto ... los <<operadores implementados se utilizan para imprimir representaciones de texto legibles por humanos de objetos, que a menudo no es lo que desea para la serialización.
einpoklum
1
@einpoklum En lugar de definir <<el genérico ostream, intente definirlo para una secuencia de archivos.
Carcigenicate
1
@Carcigenicate: un archivo de registro que toma texto legible por humanos es una secuencia de archivos.
einpoklum
1
@einpoklum No estoy muy seguro de lo que quieres decir. Sin embargo, Frank tiene razón, esos operadores pueden usarse para serializar. Acabo de definirlos para serializar / deserializar un vector.
Carcigenicate
2
Creo que el truco está aquí "Debería anular la <<salida de su objeto a algún contexto de serialización ... Cada objeto es responsable de generar su ..." - la pregunta es cómo evitar tener que escribir eso laboriosamente para cada objeto: cuánto puede idioma o bibliotecas ayuda?
ShreevatsaR
14

Recomiendo los buffers de protocolo de Google . Tuve la oportunidad de probar la biblioteca en un nuevo proyecto y es muy fácil de usar. La biblioteca está muy optimizada para el rendimiento.

Protobuf es diferente de otras soluciones de serialización mencionadas aquí en el sentido de que no serializa sus objetos, sino que genera código para los objetos que son serialización de acuerdo con sus especificaciones.

yoav.aviram
fuente
2
¿Ha tenido experiencia en la serialización de objetos de aproximadamente 10-50 MB de tamaño con esto? La documentación parece decir que las memorias intermedias de protocolo son más adecuadas para objetos de un MB de tamaño.
Agnel Kurian
Hice rodar mi propia biblioteca, no usa streams (todavía), así que es realmente para cosas pequeñas: gist.github.com/earonesty/5ba3a93f391ea03ef90884840f068767
Erik Aronesty
13

Boost :: serialización es una gran opción, pero me he encontrado con un nuevo proyecto: ¡ Cereal que me parece mucho más elegante! Le recomiendo investigarlo.

M2tM
fuente
4

Puede verificar el protocolo amef , un ejemplo de codificación C ++ en amef sería,

    //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 en Java sería como,

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

La implementación del protocolo tiene códecs tanto para C ++ como para Java, la parte interesante es que puede retener la representación de la clase de objeto en forma de pares de nombre y valor, requerí un protocolo similar en mi último proyecto, cuando accidentalmente me topé con este protocolo, en realidad modificó la biblioteca base de acuerdo con mis requisitos. Espero que esto te ayude.

Dave
fuente
2

Sugiero buscar en fábricas abstractas, que a menudo se utiliza como base para la serialización

He respondido en otra pregunta SO sobre las fábricas de C ++. Por favor mira allí si una fábrica flexible es de interés. Intento describir una forma antigua de ET ++ para usar macros que me ha funcionado muy bien.

ET ++ fue un proyecto para portar MacApp antiguo a C ++ y X11. En su esfuerzo, Eric Gamma, etc., comenzó a pensar en Patrones de diseño . ET ++ contenía formas automáticas de serialización e introspección en tiempo de ejecución.

epatel
fuente
0

Si desea un rendimiento simple y óptimo y no le importa la compatibilidad con datos anteriores, pruebe con HPS , es liviano, mucho más rápido que Boost, etc., y mucho más fácil de usar que Protobuf, etc.

Ejemplo:

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
fuente
0

Aquí hay una biblioteca de serializador simple que eliminé. Es solo encabezado, c11 y tiene ejemplos para serializar tipos básicos. Aquí hay uno para un mapa a la clase.

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

Salida:

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
fuente
0

Estoy usando la siguiente plantilla para implementar la serialización:

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

Aquí Testá el tipo que desea serializarMode es un tipo ficticio para diferenciar entre diferentes tipos de serialización, por ejemplo. el mismo número entero se puede serializar como little endian, big endian, varint, etc.

Por defecto, Serializerdelega la tarea al objeto que se está serializando. Para los tipos integrados, debe hacer una especialización de plantilla de Serializer.

También se proporcionan plantillas de funciones de conveniencia.

Por ejemplo, pequeña serialización endiana de enteros sin signo:

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

Luego para serializar:

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

Para deserializar:

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

Debido a la lógica del iterador abstracto, debería funcionar con cualquier iterador (por ejemplo, iteradores de flujo), puntero, etc.

Calmarius
fuente