2010-07-01 7 views
6

Je n'ai pas trouvé quelque chose de directement lié à la recherche, alors s'il vous plaît pardonner s'il s'agit d'un doublon.sérialiser tout type de données en tant que vecteur <uint8_t> - utiliser reinterpret_cast?

Ce que je cherche à faire est de sérialiser les données à travers une connexion réseau. Mon approche consiste à convertir tout ce que j'ai besoin de transférer vers un std::vector<uint8_t> et, du côté récepteur, décompresser les données dans les variables appropriées. Mon approche ressemble à ceci:

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

Ce que je suis en utilisant comme

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

Ma préoccupation est le

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

ligne (que je comprends faire la même chose que reinterpret_cast). Y a-t-il un meilleur moyen d'accomplir cela sans le double casting?

Mon approche naïve consistait simplement à utiliser static_cast< uint8_t* >(&data) qui a échoué. J'ai been told in the past que reinterpret_cast est mauvais. Donc, je voudrais l'éviter (ou la construction que j'ai actuellement) si possible.

Bien sûr, il y a toujours uint8_t * src = (uint8_t *)(&data).

Suggestions?

Répondre

16

Ma suggestion est d'ignorer toutes les personnes qui vous disent que reinterpret_cast est mauvais. Ils vous disent que c'est mauvais, parce que ce n'est généralement pas une bonne pratique de prendre la carte mémoire d'un type et prétendre que c'est un autre type. Mais dans ce cas, c'est exactement ce que vous voulez faire, car votre but est de transmettre la carte mémoire sous la forme d'une série d'octets.

Il est de loin préférable d'utiliser un double static_cast, car il détaille complètement le fait que vous prenez un type et que vous prétendez volontairement qu'il s'agit d'autre chose. Cette situation est exactement ce que reinterpret_cast est pour, et esquiver en l'utilisant avec un intermédiaire de pointeur void est simplement obscurcir votre sens sans bénéfice.

Aussi, je suis sûr que vous êtes au courant de cela, mais attention pour les pointeurs dans T.

1

Vous ne faites aucune encodage réelle ici, vous copiez simplement la représentation brute de les données de la mémoire dans un tableau d'octets, puis l'envoi sur le réseau. Ça ne va pas marcher. Voici un exemple rapide pour expliquer pourquoi:

struct A { 
    int a; 
}; 

struct B { 
    A* p_a; 
} 

Qu'est-ce qui se passe lorsque vous utilisez votre méthode pour envoyer un B au-dessus du réseau? Le destinataire reçoit p_a, l'adresse d'un objet A sur votre machine, mais cet objet n'est pas sur leur machine. Et même si vous leur avez envoyé l'objet A, il ne serait pas à la même adresse. Il n'y a aucun moyen qui peut fonctionner si vous envoyez juste la struct B brute. Et cela ne tient même pas compte des problèmes plus subtils comme l'endianness et la représentation en virgule flottante qui peuvent affecter la transmission de types simples tels que int et double. Ce que vous faites en ce moment n'est fondamentalement pas différent de simplement couler à uint8_t* en ce qui concerne si ça va fonctionner ou pas (ça ne marchera pas, sauf pour les cas les plus triviaux).

Ce que vous devez faire est de concevoir une méthode de sérialisation. La sérialisation signifie tout moyen de résoudre ce genre de problème: comment obtenir des objets en mémoire sur le réseau sous une forme telle qu'ils puissent être reconstruits de manière significative de l'autre côté. C'est un problème délicat, mais c'est un problème bien connu et résolu à plusieurs reprises. Voici un bon point de départ pour la lecture: http://www.parashift.com/c++-faq-lite/serialization.html

+0

Donc, oui, abus de langage. En ce qui concerne le reste de votre commentaire: la question, telle qu'elle est posée, est une simplification pour savoir si 'reinterpret_cast '(ou similaire) - je vais le renommer pour être plus spécifique. Je suis conscient des subtilités dans le transfert de données et en interne tout a un pack/unpack qui fait essentiellement ce que je décris ci-dessus pour ses propres données. – ezpz

2

Vous pouvez vous débarrasser d'une distribution en exploitant le fait que tout pointeur peut être implicitement converti en void*. En outre, vous pouvez ajouter quelques 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)); 
} 

Vous pouvez ajouter un chèque de compilation pour T être un POD, pas struct, et aucun pointeur. Cependant, l'interprétation de la mémoire d'un objet au niveau octet ne sera jamais enregistrée, point. Si vous devez le faire, faites-le dans un joli emballage (comme vous l'avez fait), et surmontez-le. Lorsque vous portez sur une plate-forme/un compilateur différent, gardez un œil sur ces choses.

+0

J'ai le 'const' là mais élidé pour la brièveté. Je n'ai pas, cependant, la vérification du pointeur et/ou struct. Ceci est utilisé seulement par moi-même, mais il serait probablement plus sûr d'ajouter ces contrôles pour être sûr. Merci. – ezpz

6

Votre situation est exactement ce que reinterpret_cast est pour, c'est plus simple qu'un double static_cast et documente clairement ce que vous faites.

Juste pour être sûr, vous devez utiliser unsigned char au lieu de uint8_t:

  • faire reinterpret_cast-unsigned char * puis déréférencement le pointeur résultant est sûr et portable et est explicitement autorisée par [basic.lval] §3.10/10
  • faire reinterpret_cast-std::uint8_t * et déréférencement le pointeur qui en résulte est une violation de la règle de stricte aliasing et est un comportement non défini si std::uint8_t est mis en œuvre comme poste Type entier non signé terminé.

    S'il existe, uint8_t doit toujours avoir la même largeur que unsigned char. Cependant, il n'est pas nécessaire que ce soit le même type; il peut s'agir d'un type entier étendu distinct. Il n'a pas besoin d'avoir la même représentation que unsigned char (voir When is uint8_t ≠ unsigned char?).

    (Ce n'est pas tout à fait hypothétique: faire [u]int8_t un type entier spécial étendu permet des optimisations agressives)

Si vous voulez vraiment uint8_t, vous pouvez ajouter un:

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

si que le code ne compilera pas sur des plateformes sur lesquelles il résulterait un comportement indéfini.

+0

+1 pour cela étant mieux que chained 'static_cast's et surtout les avertissements sur' uint8_t'. J'ai lu un article comme celui-ci, peut-être même le même, dans le passé - et j'ai rapidement dû faire beaucoup de 's/uint8_t/unsigned char/g';) –

Questions connexes