【问题标题】:How to serialize a tree structure in C++?如何在 C++ 中序列化树结构?
【发布时间】:2011-10-23 12:47:11
【问题描述】:

我正在尝试序列化/反序列化游戏场景以进行网络发送/接收以及从磁盘保存/加载。

我的游戏引擎使用节点和组件,因此它们是唯一需要序列化的对象。场景可能如下所示:

Root Node
  - Node
    - SpecializedComponent
    - SpecializedComponent
    - Node 
      - Node
  - Node
    - Node
    - Node
      - Node
        - Node
          - SpecializedComponent
  - Node

一个节点基本上是这样的:

class Node {
    map<String, Node> mChildren;
    map<String, Component> mComponents;
    uuid_t mId;
    Node* mParent;
};

SpecializedComponent 基本上是这样的:

class SpecializedComponent : public Component {
    uuid_t mId;
    Node* mNode;
};

我想使用 YAML 或 JSON 作为我的文本表示。我有 Qt、Boost 和任何其他我想要的库,因此依赖关系不是问题。其实节点已经是Q_OBJECTS所以我有反思。

尽管进行了反射,但将其正确反序列化回 C++ 树结构似乎是一个问题。

最理想的情况是,我想要一个优雅而高效的解决方案,将这样的结构序列化/反序列化为二进制或文本格式。

【问题讨论】:

  • 对于 JSON 来说似乎是一个合理的工作,并且有几个可用的包,而且您可以在不到 1000 行的源代码中编写自己的包。
  • 您的输入文件看起来很像 YAML。检查这个:stackoverflow.com/questions/365155/parse-yaml-files-in-c-c
  • 您的主要问题是:mComponents 可以NOT 持有任何SpecializedComponent 对象。容器保存实际值。因此,如果您将派生类型分配(或推送/插入)到容器中,您将遭受 The Slicing Problem 的影响。这是将对象的一个​​Component 部分复制到容器中并切掉SpecializedComponent 的额外部分的地方。要拥有异构容器,您需要使用 std::map&lt;String, Component*&gt; 之类的东西(或为指针设计的容器 boost::ptr_map&lt;String, Component&gt;
  • @LokiAstari,我已经这样做了,尽管成功有限,因为我无法 QtMetaType 为我构造一个合适的对象。

标签: c++ serialization tree


【解决方案1】:

下降递归解析器通常是处理重构部分的更简单且足够强大的选项。我可以尝试设置一些代码来具体显示,在伪代码中是这样的:

 Node *parse(stream s) {
  content = new Node;
  s >> content >> nsons;
  for (int c = 0; c < nsons; ++c)
   content->addChild(parse(s));
  return content; 
 }

当然,当读取组件时,我们必须检查类型。

【讨论】:

  • 递归序列化部分不是这里的问题,这是简单的部分。如何从它们的字符串表示中重新创建对象并再次将整个树重新创建到内存中?
  • 使用std io操作符和合适类型的构造函数:new Node或者new SpecializedComponent,通过算法得到结构(语法树的自顶向下访问)
  • @chac 展示了如何递归地反序列化(重新创建)。究竟是什么不清楚?
  • 我不一定知道我拥有的具体类型,因为它可能是任何 SpecializedComponent。我可以使用 QtMetaType 和 QtMetaObject 但我仍然需要将生成的 void* 转换为我可以使用的具体类型。
【解决方案2】:

我建议您为此使用protocol buffers library。由于您将 C++ 数据序列化以在网络上发送,因此您将对这个库所提供的功能非常有帮助。

【讨论】:

  • 我看过 protobuf,但它对递归序列化我们的树并在反序列化期间重新创建其对象有何帮助?
  • @Svenstaro:它也可以用于序列化递归数据结构
【解决方案3】:

序列化节点属性 序列化此节点的组件。 IE序列化子组件的数量,然后是每个组件。

为了序列化一个组件,您应该将一个 Id(一个 int 或一个字符串)分配给每个组件类型(这样您就可以确定如何反序列化一个特定的 hs 类型的组件) 序列化子节点个数 序列化子节点(递归调用相同的函数)

那么反序列化就是确切的镜像。 反序列化,节点属性,然后是组件的数量。 为每个组件获取组件类型,根据该类型反序列化) 等等……

如果你使用 Qt,QTextStream/QDataStream 是最简单的选择。 二进制格式在反序列化时必须是众所周知的,因此您应该在序列化内容的开头放置版本号或 Id,以便稍后添加或更改内容。

此外,如果您必须与其他语言(如 java)兼容,请注意 Qt 不会使用与字符串相同的类型规范

【讨论】:

  • 那么我如何从它们的字符串表示中重新创建 SpecializedComponents?此外,QTextStream 和 QDataStream 对于这类事情似乎太有限了,因为它们只序列化原语或充其量是预定义的 Qt 对象。
  • 如果您想要一个唯一的 ID,那么对象的地址就可以作为 ID。
【解决方案4】:

我猜你可以用Boost Serialization 做到这一点。它支持包括std::map在内的所有STL容器的序列化。

【讨论】:

  • 这不是这里的问题。没有 STL 容器像这样嵌套。我需要序列化/反序列化一个完全自定义的结构,它不仅包含原语(如 STL 容器那样),还包含自定义类。
  • @Svenstaro:如果您认为 boost 序列化不能处理这种简单的结构,那么您需要重新阅读文档。
  • @LokiAstari:这不完全是关于结构的。可能更大的问题是在反序列化时获取组件类型的正确派生类。我看不到那里的工厂周围的路。我认为我的问题足以说明这个问题。显然不是。
  • @Svenstaro - 如果还不清楚,请阅读 boost.org/doc/libs/1_47_0/libs/serialization/doc/… - 它解释了 boost 序列化库如何负责将对象的类型写入流,然后还负责读取类型并创建正确的具体对象类型的实例,以便将数据反序列化到其中。
  • 嗯,很好。我再看看。
猜你喜欢
  • 2010-10-18
  • 1970-01-01
  • 1970-01-01
  • 2012-10-22
  • 2021-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-27
相关资源
最近更新 更多