miércoles, 8 de abril de 2015

C++ - Serialización de clases

Serialización de clases

Hola de nuevo, tras tanto, tanto tiempo. En esta entrada, pondré algún ejemplo de cómo serializar una clase o estructura, para posteriormente guardarla en un archivo, enviarla por sockets, o cualquier otro fin que requiera convertir la clase o estructura a un arreglo de bytes.




Para empezar, mostrar la clase que usaré en este ejemplo:

class Prueba{
public: 
    int n;
    string s;
};

La clase Prueba tiene un campo entero, y otro campo string.

En caso de haber solo campos con tipos básicos, como por ejemplo 3 int, existe la posibilidad de hacer un casting del objeto a char*, de este modo:

Prueba1 p;
char* c = (char*)&p; // Tamaño: sizeof(p)

Convirtiendo la dirección del objeto a char* obtendremos una serialización inmediata. Pero, en caso de haber otras clases, o punteros dentro de esta clase, no es posible hacerlo mediante este método. La razón es simple: un campo int* solo guarda una dirección de memoria, y no los valores int.

Así que para serializar (y luego poder deserializar), estableceremos unas reglas. Los números, los guardaremos en formato binario, no en representación decimal.

  • Datos primitivos, como int o double: Los guardaremos tal como están en memoria, es decir, copiaremos su memoria directamente. Estos son los tipos de datos en los que funciona este método.
  • String y char*: Guardaremos, en primer lugar, un entero sin signo representando el número de caracteres que almacenan estas variables. Luego, naturalmente, los caracteres.
Esas reglas bastan para lo que se trata aquí. vamos al código:

class Prueba{
public:
    int n;
    string s;

    string serializar(){
        string t;
    
        // int n
        t.append((char*)&n, sizeof(n));

        // string s
        unsigned int size = s.size();
        t.append((char*)&size, sizeof(size));
        t.append(s.c_str(), size);
    
        return t;
    }
};

Al guardar el tamaño de la variable string, hemos antes guardado su tamaño en una variable a parte, ya que necesitamos acceder a sus bytes para guardarla de forma binaria. Si el campo que queremos serializar es una clase que también tiene su propio método para serializar, basta guardar su serialización en una variable string, y serializar esa string como tal (Numero de caracteres, Caracteres).

Si se guarda el número de elementos siempre antes de un arreglo o de una string, es para luego, a la hora de deserializar, poder leer del archivo la cantidad justa de bytes (Nº bytes = Nº de elementos * Bytes por elemento).


Y hasta aquí esta entrada. Es una pregunta muy recurrente que me he encontrado, y quería dar una explicación por aquí, "para que quede por si acaso". Obvio decir que este método de serialización es el que uso yo, pero habrá más, y cada uno puede serializar como quiera, según la clase, sus campos, etc. Lo más importante al serializar, es que se pueda deserializar, obteniendo un objeto idéntico al serializado.

2 comentarios: