代码之家  ›  专栏  ›  技术社区  ›  ezpz

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

  •  6
  • ezpz  · 技术社区  · 14 年前

    我在搜索中没有发现任何直接相关的内容,因此如果这是一个副本,请原谅。

    我要做的是通过网络连接序列化数据。我的方法是将我需要传输的所有内容转换为 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 那个 重新解释铸模 是坏的。因此,如果可能的话,我想避免使用它(或者我目前使用的构造)。

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

    建议?

    4 回复  |  直到 10 年前
        1
  •  16
  •   jdmichal    14 年前

    我的建议是忽略所有告诉你的人 reinterpret_cast 是坏的。他们告诉你这是不好的,因为把一种类型的内存图当作另一种类型通常不是一个好的实践。但在本例中,这正是您想要做的,因为您的整个目的是以一系列字节的形式传输内存映射。

    比用双人床好得多- static_cast 因为它完全详细地说明了一个事实,即你选择了一种类型,并且有目的地假装它是另一种类型。这种情况正是 重新解释铸模 是的,而回避使用它与一个空指针中介只是掩盖了你的意思,没有任何好处。

    另外,我相信你知道这一点,但是注意T中的指针。

        2
  •  8
  •   Community CDub    7 年前

    你的处境正是 reinterpret_cast 是的,比双人房简单 static_cast 清楚地记录你在做什么。

    为了安全起见,你应该使用 unsigned char 而不是 uint8_t :

    • 重新解释铸模 unsigned char * 然后取消对结果指针的引用是安全的和可移植的,并且 [基本.lval]第3.10/10节
    • 重新解释铸模 std::uint8_t * 然后取消对结果指针的引用违反了严格的别名规则,如果 std::uint8_t 实现为扩展无符号整数类型。

      如果存在, UIT88T 宽度必须始终与 无符号字符 . 但是,它不必是相同的类型;它可以是一个不同的扩展整数类型。它也不需要与 无符号字符 (见 When is uint8_t ≠ unsigned char? )

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

    如果你真的想 UIT88T ,您可以添加:

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

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

        3
  •  2
  •   sbi    14 年前

    通过利用任何指针都可以隐式转换为 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 作为荚果,没有 struct, 没有指针。

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

        4
  •  1
  •   Tyler McHenry    14 年前

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

    struct A {
      int a;
    };
    
    struct B {
      A* p_a;
    }
    

    当您使用您的方法发送 B 通过网络?收件人收到 p_a ,一些人的地址 A 对象在您的计算机上,但该对象不在其计算机上。即使你把 对象也不在同一地址。如果你只是把生的送去,那就没有办法了 结构。这甚至还没有考虑到更微妙的问题,比如endianness和浮点表示,这些问题会影响简单类型的传输,比如 int double .

    你现在所做的基本上和你现在所做的一样 uint8_t* 至于它是否会起作用(除了最微不足道的情况外,它不会起作用)。

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