2010-07-01 7 views
6

No encontré nada directamente relacionado en la búsqueda, así que por favor perdone si es un duplicado.serialize any data type como vector <uint8_t> - use reinterpret_cast?

Lo que quiero hacer es serializar los datos a través de una conexión de red. Mi enfoque es convertir todo lo que necesito para transferir a un std::vector<uint8_t> y en el lado de recepción descomprimir los datos en las variables adecuadas. Mi enfoque es el siguiente:

template <typename T> 
inline void pack (std::vector<uint8_t>& dst, T& data) { 
    uint8_t * src = static_cast < uint8_t* >(static_cast < void * >(&data)); 
    dst.insert (dst.end(), src, src + sizeof (T)); 
} 

template <typename T> 
inline void unpack (vector <uint8_t >& src, int index, T& data) { 
    copy (&src[index], &src[index + sizeof (T)], &data); 
} 

Qué estoy usando como

vector<uint8_t> buffer; 
uint32_t foo = 103, bar = 443; 
pack (buff, foo); 
pack (buff, bar); 

// And on the receive side 
uint32_t a = 0, b = 0; 
size_t offset = 0; 
unpack (buffer, offset, a); 
offset += sizeof (a); 
unpack (buffer, offset, b); 

Mi preocupación es la

uint8_t * src = static_cast < uint8_t* >(static_cast < void * >(&data));

línea

(que entiendo que hacer lo mismo que reinterpret_cast) ¿Hay una mejor manera de lograr esto sin el doble elenco?

Mi enfoque ingenuo era simplemente usar static_cast< uint8_t* >(&data) que falló. Tengo been told in the past que reinterpret_cast es malo. Así que me gustaría evitarlo (o la construcción que tengo actualmente) si es posible. Por favor, siempre hay uint8_t * src = (uint8_t *)(&data).

Sugerencias?

Respuesta

16

Mi sugerencia es ignorar a todas las personas que le dicen que reinterpret_cast es malo. Te dicen que es malo, porque generalmente no es una buena práctica tomar el mapa de memoria de un tipo y pretender que es de otro tipo. Pero en este caso, eso es exactamente lo que quiere hacer, ya que su objetivo es transmitir el mapa de memoria como una serie de bytes.

Es mucho mejor que usar un doble static_cast, ya que detalla por completo el hecho de que está tomando un tipo y pretenden a propósito que sea otra cosa. Esta situación es exactamente lo que es reinterpret_cast, y eludir su uso con un intermediario de puntero vacío es simplemente oscurecer su significado sin ningún beneficio.

Además, estoy seguro de que usted es consciente de esto, pero cuidado para los punteros en T.

1

Usted no está haciendo ninguna codificación real aquí, sólo estás copiando la representación cruda de los datos de la memoria en una matriz de bytes y luego enviarlos a través de la red. Eso no va a funcionar. Aquí está un ejemplo rápido de por qué:

struct A { 
    int a; 
}; 

struct B { 
    A* p_a; 
} 

¿Qué ocurre cuando se utiliza el método para enviar un B a lo largo de la red? El destinatario recibe p_a, la dirección de algún objeto A en su máquina, pero ese objeto no está en su máquina. E incluso si les envió el objeto A también, no estaría en la misma dirección. No hay forma de que funcione si solo envía la estructura B sin procesar. Y eso ni siquiera considera cuestiones más sutiles como el endianness y la representación de punto flotante que pueden afectar la transmisión de tipos simples como int y double.

Lo que está haciendo ahora mismo no es fundamentalmente diferente de simplemente emitir a uint8_t* en cuanto a si va a funcionar o no (no funcionará, excepto en los casos más triviales).

Lo que necesita hacer es idear un método de serialización . Serialización significa cualquier forma de resolver este tipo de problema: cómo sacar objetos de la memoria a la red de forma tal que puedan reconstruirse de manera significativa en el otro lado. Este es un problema difícil, pero es un problema bien conocido y resuelto repetidamente. Este es un buen punto de partida para leer: http://www.parashift.com/c++-faq-lite/serialization.html

+0

Así que, sí, mal nombre. Respecto al resto de su comentario: la pregunta, tal como se plantea, es una simplificación para preguntar si se debe o no reinterpretar_cast' (o similar) - Cambiaré el nombre para ser más específico. Soy consciente de las sutilezas en la transferencia de datos e internamente todo tiene un paquete/desempaquetar que esencialmente hace lo que describo arriba para sus propios datos. – ezpz

2

Puede deshacerse de un elenco explotando el hecho de que cualquier puntero se puede convertir implícitamente en void*. Además, es posible que desee añadir un poco const:

//Beware, brain-compiled code ahead! 
template <typename T> 
inline void encode (std::vector<uint8_t>& dst, const T& data) 
{ 
    const void* pdata = &data; 
    uint8_t* src = static_cast<uint8_t*>(pdata); 
    dst.insert(dst.end(), src, src + sizeof(T)); 
} 

Es posible que desee agregar una comprobación en tiempo de compilación para T ser un POD, sin struct, y ningún puntero.

Sin embargo, interpretar la memoria de algún objeto en el nivel de byte nunca será guardado, punto. Si tiene que hacerlo, hágalo en un envoltorio agradable (como lo ha hecho) y superéntelo. Cuando realice un puerto a una plataforma/compilador diferente, tenga en cuenta estas cosas.

+0

Tengo el 'const' pero elide para abreviar. Sin embargo, no tengo el control de puntero y/o estructura. Solo yo lo uso, pero probablemente sería más seguro agregar esos controles para estar seguro. Gracias. – ezpz

6

Su situación es exactamente para lo que es reinterpret_cast, es más simple que un doble static_cast y documenta claramente lo que está haciendo.

Sólo para estar seguro, usted debe utilizar en lugar de unsigned charuint8_t:

  • haciendo reinterpret_cast a unsigned char * y luego dereferencing el puntero resultante es segura y portátil y permite expresamente [basic.lval] §3.10/10
  • haciendo reinterpret_cast-std::uint8_t * y luego dereferencing el puntero resultante es una violación de la regla de alias estricto y es un comportamiento indefinido si std::uint8_t se implementa como ext finalizó el tipo entero sin signo.

    Si existe, uint8_t siempre debe tener el mismo ancho que unsigned char. Sin embargo, no necesita ser del mismo tipo; puede ser un tipo entero extendido distinto. Tampoco necesita tener la misma representación que unsigned char (ver When is uint8_t ≠ unsigned char?).

    (Esto no es completamente hipotética: hacer [u]int8_t un tipo entero especial ampliada permite algunas optimizaciones agresivas)

Si realmente quiere uint8_t, se podrían añadir un:

static_assert(std::is_same<std::uint8_t, unsigned char>::value, 
       "We require std::uint8_t to be implemented as unsigned char"); 

por lo que el código no se compilará en plataformas en las que daría lugar a un comportamiento indefinido.

+0

+1 para que esto sea mejor que '' static_cast's encadenados y especialmente las advertencias sobre 'uint8_t'. Leí una publicación como esta, tal vez incluso la misma, en el pasado, y rápidamente tuve que hacer un montón de 's/uint8_t/unsigned char/g';) –

Cuestiones relacionadas