Tengo una pequeña jerarquía de objetos que necesito serializar y transmitir a través de una conexión de socket. Necesito serializar el objeto y luego deserializarlo según el tipo que sea. ¿Existe una manera fácil de hacer esto en C ++ (como la hay en Java)?
¿Hay ejemplos o tutoriales de código en línea de serialización de C ++?
EDITAR: Para que quede claro, estoy buscando métodos para convertir un objeto en una matriz de bytes y luego volver a convertirlo en un objeto. Puedo manejar la transmisión de socket.
c++
serialization
marshalling
c++-faq
Bill el lagarto
fuente
fuente
Respuestas:
Hablando de serialización, me viene a la mente la API de serialización boost . En cuanto a transmitir los datos serializados a través de la red, usaría sockets de Berkeley o la biblioteca asio .
Editar:
si desea serializar sus objetos en una matriz de bytes, puede usar el serializador boost de la siguiente manera (tomado del sitio del tutorial):
#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) {} };
La serialización real es entonces bastante fácil:
#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 }
La deserialización funciona de manera análoga.
También hay mecanismos que le permiten manejar la serialización de punteros (estructuras de datos complejas como árboles, etc. no son un problema), clases derivadas y puede elegir entre serialización binaria y de texto. Además, todos los contenedores STL son compatibles desde el primer momento.
fuente
En algunos casos, cuando se trata de tipos simples, puede hacer:
object o; socket.write(&o, sizeof(o));
Está bien como prueba de concepto o como primer borrador, para que otros miembros de su equipo puedan seguir trabajando en otras partes.
Pero tarde o temprano, generalmente antes , ¡esto te hará daño!
Tiene problemas con:
(Además, necesita saber en qué está desempacando en el lado de recepción).
Puede mejorar esto desarrollando sus propios métodos de ordenación / desglose para cada clase. (Idealmente virtuales, para que puedan extenderse en subclases). Unas pocas macros simples le permitirán escribir diferentes tipos básicos con bastante rapidez en un orden neutral de endian grande / pequeño.
Pero ese tipo de trabajo pesado es mucho mejor y más fácil de manejar a través de la biblioteca de serialización de boost .
fuente
Existe un patrón genérico que puede utilizar para serializar objetos. La primitiva fundamental son estas dos funciones que puede leer y escribir desde iteradores:
template <class OutputCharIterator> void putByte(char byte, OutputCharIterator &&it) { *it = byte; ++it; } template <class InputCharIterator> char getByte(InputCharIterator &&it, InputCharIterator &&end) { if (it == end) { throw std::runtime_error{"Unexpected end of stream."}; } char byte = *it; ++it; return byte; }
Luego, las funciones de serialización y deserialización siguen el patrón:
template <class OutputCharIterator> void serialize(const YourType &obj, OutputCharIterator &&it) { // Call putbyte or other serialize overloads. } template <class InputCharIterator> void deserialize(YourType &obj, InputCharIterator &&it, InputCharIterator &&end) { // Call getByte or other deserialize overloads. }
Para las clases, puede usar el patrón de función de amigo para permitir que la sobrecarga se encuentre usando ADL:
class Foo { int internal1, internal2; // So it can be found using ADL and it accesses private parts. template <class OutputCharIterator> friend void serialize(const Foo &obj, OutputCharIterator &&it) { // Call putByte or other serialize overloads. } // Deserialize similar. };
En su programa, puede serializar y objetar en un archivo como este:
std::ofstream file("savestate.bin"); serialize(yourObject, std::ostreambuf_iterator<char>(file));
Entonces lee:
std::ifstream file("savestate.bin"); deserialize(yourObject, std::istreamBuf_iterator<char>(file), std::istreamBuf_iterator<char>());
Mi vieja respuesta aquí:
La serialización significa convertir su objeto en datos binarios. Mientras que la deserialización significa recrear un objeto a partir de los datos.
Al serializar, está insertando bytes en un
uint8_t
vector. Al anular la serialización, está leyendo bytes de unuint8_t
vector.Ciertamente, hay patrones que puede emplear al serializar cosas.
Cada clase serializable debe tener una
serialize(std::vector<uint8_t> &binaryData)
función firmada o similar que escribirá su representación binaria en el vector proporcionado. Entonces, esta función puede pasar este vector a las funciones de serialización de sus miembros para que también puedan escribir sus cosas en él.Dado que la representación de datos puede ser diferente en diferentes arquitecturas. Necesita encontrar un esquema de cómo representar los datos.
Empecemos por lo básico:
Serializar datos enteros
Simplemente escriba los bytes en orden little endian. O use la representación de varint si el tamaño importa.
Serialización en orden little endian:
data.push_back(integer32 & 0xFF); data.push_back((integer32 >> 8) & 0xFF); data.push_back((integer32 >> 16) & 0xFF); data.push_back((integer32 >> 24) & 0xFF);
Deserialización del orden little endian:
integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
Serializar datos de coma flotante
Hasta donde yo sé, el IEEE 754 tiene un monopolio aquí. No conozco ninguna arquitectura convencional que use algo más para flotadores. Lo único que puede ser diferente es el orden de los bytes. Algunas arquitecturas usan little endian, otras usan un orden de bytes big endian. Esto significa que debe tener cuidado con el orden en el que se emiten los bytes en el extremo receptor. Otra diferencia puede ser el manejo de los valores denormal e infinito y NAN. Pero siempre que evite estos valores, debería estar bien.
Publicación por entregas:
uint8_t mem[8]; memcpy(mem, doubleValue, 8); data.push_back(mem[0]); data.push_back(mem[1]); ...
La deserialización lo está haciendo al revés. ¡Cuidado con el orden de bytes de tu arquitectura!
Serializar cadenas
Primero debes acordar una codificación. UTF-8 es común. Luego, guárdelo como una longitud prefijada: primero almacena la longitud de la cadena usando un método que mencioné anteriormente, luego escribe la cadena byte por byte.
Serializar matrices.
Son lo mismo que las cuerdas. Primero serializa un número entero que representa el tamaño de la matriz y luego serializa cada objeto en él.
Serializar objetos completos
Como dije antes, deberían tener un
serialize
método que agregue contenido a un vector. Para anular la serialización de un objeto, debe tener un constructor que tome un flujo de bytes. Puede ser unistream
pero, en el caso más simple, puede ser solo unuint8_t
puntero de referencia . El constructor lee los bytes que quiere de la secuencia y configura los campos en el objeto. Si el sistema está bien diseñado y serializa los campos en el orden de los campos del objeto, simplemente puede pasar la secuencia a los constructores del campo en una lista de inicializadores y deserializarlos en el orden correcto.Serializar gráficos de objetos
Primero debe asegurarse de que estos objetos sean realmente algo que desee serializar. No necesita serializarlos si hay instancias de estos objetos presentes en el destino.
Ahora descubrió que necesita serializar ese objeto apuntado por un puntero. El problema de los punteros que son válidos solo en el programa que los usa. No puede serializar punteros, debe dejar de usarlos en objetos. En su lugar, cree grupos de objetos. Este grupo de objetos es básicamente una matriz dinámica que contiene "cajas". Estos cuadros tienen un recuento de referencias. El recuento de referencia distinto de cero indica un objeto vivo, cero indica un espacio vacío. Luego, crea un puntero inteligente similar al shared_ptr que no almacena el puntero al objeto, sino el índice en la matriz. También debe acordar un índice que denote el puntero nulo, por ejemplo. -1.
Básicamente, lo que hicimos aquí fue reemplazar los punteros con índices de matriz. Ahora, al serializar, puede serializar este índice de matriz como de costumbre. No necesita preocuparse de dónde estará el objeto en la memoria en el sistema de destino. Solo asegúrate de que también tengan el mismo grupo de objetos.
Entonces necesitamos serializar los grupos de objetos. Pero cuales? Bueno, cuando serializa un gráfico de objeto, no está serializando solo un objeto, está serializando un sistema completo. Esto significa que la serialización del sistema no debe comenzar desde partes del sistema. Esos objetos no deberían preocuparse por el resto del sistema, solo necesitan serializar los índices de la matriz y eso es todo. Debe tener una rutina de serializador del sistema que orquesta la serialización del sistema y recorra los grupos de objetos relevantes y serialice todos.
En el extremo receptor, todas las matrices y los objetos dentro se deserializan, recreando el gráfico de objeto deseado.
Punteros de función de serialización
No almacene punteros en el objeto. Tenga una matriz estática que contenga los punteros a estas funciones y almacene el índice en el objeto.
Dado que ambos programas tienen esta tabla compilada en sus estantes, usar solo el índice debería funcionar.
Serializar tipos polimórficos
Como dije que debería evitar los punteros en tipos serializables y que debería usar índices de matriz en su lugar, el polimorfismo simplemente no puede funcionar porque requiere punteros.
Necesita solucionar esto con etiquetas de tipo y uniones.
Control de versiones
Además de todo lo anterior. Es posible que desee que interoperen diferentes versiones del software.
En este caso, cada objeto debe escribir un número de versión al comienzo de su serialización para indicar la versión.
Al cargar el objeto en el otro lado, los objetos más nuevos pueden manejar las representaciones más antiguas, pero los más antiguos no pueden manejar las más nuevas, por lo que deberían lanzar una excepción al respecto.
Cada vez que algo cambia, debes aumentar el número de versión.
Entonces, para terminar, la serialización puede ser compleja. Pero, afortunadamente, no necesita serializar todo en su programa, la mayoría de las veces solo se serializan los mensajes del protocolo, que a menudo son estructuras viejas. Así que no necesitas los complejos trucos que mencioné anteriormente con demasiada frecuencia.
fuente
A modo de aprendizaje, escribí un simple serializador de C ++ 11. Había probado varias de las otras ofertas más pesadas, pero quería algo que realmente pudiera entender cuando saliera mal o no se compilara con la última versión de g ++ (lo que me sucedió con Cereal; una biblioteca realmente agradable pero compleja y no pude asimilar los errores que el compilador arrojó en la actualización). De todos modos, es solo el encabezado y maneja tipos de POD, contenedores, mapas, etc. Sin control de versiones y solo cargará archivos del mismo arco en el que se guardó.
https://github.com/goblinhack/simple-c-plus-plus-serializer
Uso de ejemplo:
#include "c_plus_plus_serializer.h" static void serialize (std::ofstream out) { char a = 42; unsigned short b = 65535; int c = 123456; float d = std::numeric_limits<float>::max(); double e = std::numeric_limits<double>::max(); std::string f("hello"); out << bits(a) << bits(b) << bits(c) << bits(d); out << bits(e) << bits(f); } static void deserialize (std::ifstream in) { char a; unsigned short b; int c; float d; double e; std::string f; in >> bits(a) >> bits(b) >> bits(c) >> bits(d); in >> bits(e) >> bits(f); }
fuente