有没有一种优雅的方法可以将动态长度的字节缓冲区解析为结构?

Is there an elegant way of parsing a byte buffer of dynamic length into a struct?

背景

如此处所述https://godbolt.org/z/xaf95qWee(与下面的代码大致相同),我正在使用一个以内存映射文件形式提供共享内存资源的库。

对于静态大小的消息,读取方法可以非常优雅地return一个结构(匹配缓冲区的布局)并且客户端有一个很好的类型化接口,而不必担心内部结构。

template<typename DataType>
struct Message{
    //Metadata
    std::uint32_t type;
    std::uint32_t error;
    DataType data;
};
struct FixedLengthData{
    std::int32_t height;
    std::int32_t age;
};
MessageType Read(){
    MessageType msg;
    std::memcpy(&msg, rawBuffer, sizeof msg);
    return msg;
}
const auto receivedMsg = Read<Message<FixedLengthData>>();

问题/问题

然而,一些数据有效载荷构成动态数组,编码为缓冲区包含数组 S 的大小(即条目数),后跟一些已知类型(通常是整数)的 S 条目。 因此,一个示例可能如下所示: [类型|类型|错误|错误|大小(例如4)|elem|elem|elem|elem|undef|...]

我在想,在这个动态结构中是否有一种类似优雅的读取方式,只有在收到消息时才知道大小。

struct DynamicLengthData{
    std::uint32_t size;
    std::array<std::int32_t, size> data; //obviously doesn't work.
};

我考虑过的 一种想法是使用 std::vector 成员定义动态数据。这种方法的“问题”是向量的数据在堆上,而不是堆栈上。因此“直接”初始化将不起作用。当然,我可以在没有向量的情况下定义结构,直到 size 成员。然后在第二步中读取大小并专门从缓冲区中读取那么多整数,从偏移量开始。但是我一直在寻找一种没有第二步的方法。

struct StaticPartOfDynamicData{
  //possibly other members
  std::uint32_t size;
};
const auto msg = Read<Message<StaticPartOfDynamicData>>();
std::vector<std::int32_t> dynamicData;
// for 0 to msg.data.size fill vector by reading from buffer at offset sizeof(type + error + otherData + size)

另一个想法:因为缓冲区有最大大小,所以我可以创建一个尽可能大的 c 数组成员。这将能够直接初始化,但大部分数组都是空的,这听起来效率不高(我知道不能过早优化,但这主要是一个理论问题,而不是生产系统)。

我如何在我的代码中处理它的示例。

class packet
{
public:
  packet(absl::Span<const char> data)
  {
    auto current = data.data();
    std::memcpy(&length_, current, sizeof(length_));
    std::advance(current, sizeof(length_));
  
    vec_.reserve(length_);
    vec_.assign(current, current + length_);
  }
  
  //public stuff as needed

private:
  std::vector<char> vec_{};
  uint16_t length_{};
  //...other members
};

要反序列化对象,您所要做的就是 packet{{data_ptr, data_len}};

我有一个辅助函数,它删除了反序列化多个成员的大量重复和样板,但它对示例并不重要。

这应该很适合您的读取方法

MessageType Read(){
    return MessageType{{rawBuffer, sizeof msg}};
}