序列化任何数据类型为vector <uint8_t> - 使用reinterpret_cast?</uint8_t>

时间:2010-07-01 20:15:48

标签: c++ templates serialization network-programming

我没有找到与搜索直接相关的任何内容,所以请原谅这是否重复。

我要做的是通过网络连接序列化数据。我的方法是将我需要传输的所有内容转换为std::vector< uint8_t >,并在接收方将数据解压缩到适当的变量中。我的方法看起来像这样:

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

我正在使用

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

我关心的是

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

行(我理解与reinterpret_cast相同)。如果没有双重演员,是否有更好的方法来实现这一目标?

我天真的方法就是使用失败的static_cast< uint8_t* >(&data)。我been told in the past表示reinterpret_cast不好。所以我想尽可能避免它(或我目前的构造)。

当然,总有uint8_t * src = (uint8_t *)(&data)

建议?

4 个答案:

答案 0 :(得分:16)

我的建议是忽略所有告诉你reinterpret_cast不好的人。他们告诉你它很糟糕,因为采用一种类型的内存映射并假装它是另一种类型通常不是一个好习惯。但在这种情况下,这正是你想要做的,因为你的整个目的是将内存映射作为一系列字节传输。

它比使用双倍static_cast好得多,因为它完全详细说明了你正在采用一种类型而且故意假装它是其他类型的事实。这种情况正是reinterpret_cast的用途,并且使用虚拟指针中介来避免使用它只是模糊了你的意思而没有任何好处。

另外,我确信你已经意识到了这一点,但请注意T中的指示。

答案 1 :(得分:8)

您的情况正是reinterpret_cast的用途,它比双static_cast更简单,并清楚地记录您正在做的事情。

为安全起见,您应使用unsigned char代替uint8_t

  • 执行reinterpret_castunsigned char *然后取消引用结果指针是安全且可移植的,并且 [basic.lval]§3.10/ 10
  • 明确允许
  • 执行reinterpret_caststd::uint8_t *然后取消引用结果指针违反了严格别名规则,并且如果std::uint8_t被实现为扩展无符号整数类型,则是未定义的行为。< / p>

    如果存在,uint8_t必须始终与unsigned char具有相同的宽度。但是,它不一定是同一类型;它可能是一个独特的扩展整数类型。它也不必具有与unsigned char相同的表示形式(参见When is uint8_t ≠ unsigned char?)。

    (这不是完全假设:使[u]int8_t特殊的扩展整数类型允许一些积极的优化)

如果您真的想要uint8_t,可以添加:

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

这样代码就不会在导致未定义行为的平台上编译。

答案 2 :(得分:2)

你可以通过利用任何指针可以隐式地转换为void*的事实来摆脱一个演员。此外,您可能还想添加一些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));
}

您可能希望添加T作为POD,没有struct,且没有指针的编译时检查。

但是,在字节级解释某个对象的内存永远不会保存,句点。如果你必须这样做,那就用一个漂亮的包装器(就像你做的那样)来做,并克服它。当您移植到不同的平台/编译器时,请关注这些事情。

答案 3 :(得分:1)

你在这里没有做任何实际的编码,你只是将数据的原始表示从内存复制到一个字节数组,然后通过网络发送出去。那不行。以下是一个简单的例子:

struct A {
  int a;
};

struct B {
  A* p_a;
}

使用您的方法通过网络发送B时会发生什么?收件人会收到p_a,即您计算机上某个A对象的地址,但该对象不在其计算机上。即使你发送了A对象,它也不会在同一个地址。如果您只发送原始B结构,则无法工作。而且甚至没有考虑更多微妙的问题,如字节顺序和浮点表示,它们会影响{​​{1}}和int等简单类型的传输。

你现在正在做的事情与根据它是否会起作用而被投射到double基本上没有区别(除了最微不足道的情况之外,它不会起作用)。 / p>

您需要做的是设计序列化的方法。序列化意味着解决这类问题的任何方法:如何将内存中的对象以一种形式存储到网络中,以便它们可以在另一侧进行有意义的重构。这是一个棘手的问题,但它是一个众所周知且反复解决的问题。这是一个很好的阅读起点:http://www.parashift.com/c++-faq-lite/serialization.html