orientado - programación orientada a objetos en c++
¿Cómo serializas un objeto en C++? (3)
En algunos casos, cuando se trata de tipos simples, puede hacer:
object o;
socket.write(&o, sizeof(o));
Eso está bien como una prueba de concepto o primer borrador, para que otros miembros de tu equipo puedan seguir trabajando en otras partes.
Pero tarde o temprano, generalmente antes , ¡esto te hará daño!
Te encuentras con problemas con:
- Las tablas de puntero virtuales estarán dañadas.
- Los punteros (a datos / miembros / funciones) estarán dañados.
- Diferencias en relleno / alineación en diferentes máquinas.
- Problemas de ordenamiento de byte Big / Little-Endian.
- Variaciones en la implementación de float / double.
(Además, necesita saber en qué está desempacando en el lado receptor).
Puede mejorar esto desarrollando sus propios métodos de clasificación / desemparejamiento para cada clase. (Idealmente virtuales, para que puedan extenderse en subclases). Unas simples macros te permitirán escribir diferentes tipos básicos con bastante rapidez en un orden grande / pequeño-endian-neutral.
Pero ese tipo de trabajo gruñón es mucho mejor, y más fácil, manejado a través de la biblioteca de serialización de boost .
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, luego deserializarlo según el tipo que sea. ¿Hay alguna manera fácil de hacer esto en C ++ (como en Java)?
¿Hay algún ejemplo de código en línea de serialización en C ++ o tutoriales?
EDITAR: para ser claros, estoy buscando métodos para convertir un objeto en una matriz de bytes y luego volver a un objeto. Puedo manejar la transmisión de socket.
Hablando de serialización, me viene a la mente la API de serialización de impulso . En cuanto a la transmisión de los datos serializados a través de la red, utilizaría los enchufes de Berkeley o la biblioteca asio .
Editar:
Si desea serializar sus objetos en una matriz de bytes, puede usar el serializador de impulso de la siguiente manera (tomado del sitio 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 complejos como tress, 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 con la caja.
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á empujando los bytes en un vector uint8_t
. Al deserializar está leyendo bytes de un vector uint8_t
.
Ciertamente hay patrones que puede emplear al serializar cosas.
Cada clase serializable debe tener una serialize(std::vector<uint8_t> &binaryData)
o una función similar serialize(std::vector<uint8_t> &binaryData)
que escribirá su representación binaria en el vector proporcionado. Entonces esta función puede pasar este vector a sus funciones de serialización para que también puedan escribir sus cosas.
Dado que la representación de datos puede ser diferente en diferentes arquitecturas. Necesita encontrar un esquema de cómo representar los datos.
Comencemos desde lo básico:
Serializar datos enteros
Simplemente escriba los bytes en un pequeño orden endian. O use la representación de varints si el tamaño importa.
Serialización en pequeño orden 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 pequeño orden endian:
integer32 = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
Serialización de datos de coma flotante
Por lo que sé, el IEEE 754 tiene un monopolio aquí. No conozco ninguna arquitectura convencional que use algo más para carrozas. Lo único que puede ser diferente es el orden de bytes. Algunas arquitecturas usan little endian, otras usan orden de bytes big endian. Esto significa que debes tener cuidado con qué orden haces clic en los bytes en el extremo receptor. Otra diferencia puede ser el manejo de los valores denormal e infinito y NAN. Pero siempre que evites estos valores, deberías 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 hacia atrás. ¡Ten en cuenta el orden de bytes de tu arquitectura!
Series de serialización
Primero necesita acordar una codificación. UTF-8 es común. Luego guárdelo como una forma prefijada de longitud: primero almacena la longitud de la cadena usando un método que mencioné anteriormente, luego escriba la secuencia byte a byte.
Serializar matrices.
Son lo mismo que una cadena. Primero serialice un número entero que represente el tamaño de la matriz y luego serialice cada objeto en ella.
Serializar objetos enteros
Como dije antes, deberían tener un método de serialize
que agregue contenido a un vector. Para deserializar un objeto, debe tener un constructor que tome la secuencia de bytes. Puede ser istream
pero en el caso más simple puede ser solo un puntero uint8_t
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 campo del objeto, puede simplemente pasar la secuencia a los constructores del campo en una lista de inicializadores y hacer que se deserialicen en el orden correcto.
Serializar gráficos de objetos
Primero debe asegurarse de que estos objetos realmente sean algo que quiera serializar. No necesita serializarlos si las instancias de estos objetos están presentes en el destino.
Ahora descubrió que necesita serializar ese objeto apuntado por un puntero. El problema de los punteros es que solo son válidos en el programa que los usa. No puede serializar el puntero, debe dejar de usarlos en los objetos. En su lugar crea grupos de objetos. Este grupo de objetos es básicamente una matriz dinámica que contiene "cajas". Estos cuadros tienen un recuento de referencia. La cuenta de referencia distinta de cero indica un objeto activo, cero indica una ranura vacía. Luego, crea un puntero inteligente similar al shared_ptr que no almacena el puntero al objeto, sino el índice de la matriz. También necesita acordar un índice que denote el puntero nulo, ej. -1.
Básicamente lo que hicimos aquí es reemplazar los punteros con índices de matriz. Ahora, al serializar, puede serializar este índice de matriz como de costumbre. No necesita preocuparse acerca de dónde estará el objeto en la memoria en el sistema de destino. Solo asegúrate de que tengan el mismo grupo de objetos también.
Entonces necesitamos serializar los grupos de objetos. ¿Pero cuáles? Bueno, cuando serializas un gráfico de objetos, no estás serializando solo un objeto, estás 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 matriz y eso es todo. Debe tener una rutina de serializador de sistema que orquene la serialización del sistema y recorra los grupos de objetos relevantes y serialice todos ellos.
En el extremo receptor, todas las matrices y los objetos internos se deserializan, recreando el gráfico de objeto deseado.
Punteros de función de serialización
No almacene punteros en el objeto. Tener una matriz estática que contiene los punteros a estas funciones y almacenar el índice en el objeto.
Dado que ambos programas tienen esta tabla compilada en estas estanterías, debería funcionar solo con el índice.
Serializar tipos polimórficos
Como dije, debe evitar los punteros en tipos serializables y, en su lugar, debe usar índices de matriz, el polimorfismo simplemente no puede funcionar, porque requiere punteros.
Necesita trabajar esto con etiquetas de tipo y uniones.
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 el más nuevo, por lo que deben emitir una excepción al respecto.
Cada vez que algo cambia, debes encontrar el número de versión.
Entonces, para concluir, 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 de protocolo, que a menudo son simples estructuras antiguas. Entonces no necesitas los trucos complejos que mencioné anteriormente muy a menudo.