【问题标题】:boost::serialization and cyclic reference deserializationboost::serialization 和循环引用反序列化
【发布时间】:2013-12-09 14:14:07
【问题描述】:

我有一个需要序列化的树状结构。典型结构,每个节点都有parent 成员和children 向量。 parent 是指向类的原始指针,childrenvectors 的 shared_ptrs。现在看来序列化工作正常,但 de-序列化使parent 成员未初始化(指向0xcccccccc0x00000000 的指针)。

parent 成员在实际父对象尚未完成反序列化时被加载,即通过父对象 children 的反序列化请求加载子对象的 parent 成员。由于这是循环的,我想知道是否需要采取特殊措施才能使其正常工作。

感谢您的帮助。

更新:这是我的序列化函数的样子:

template <typename Archive>
void serialize(Archive& archive, GameCore::GameObject& t, const unsigned int version)
{
    archive & boost::serialization::base_object<GameCore::Object>(t);
    archive & boost::serialization::base_object<GameCore::Updatable>(t);
    archive & t.parent;
    archive & t.transform;
    archive & t.components;
    archive & t.children;
}

如果我注释掉 archive &amp; t.childrenparent 会正确填充。

更新 2: 好的,我已设法将其转换为显示问题的最小示例。以下应该编译:

#include <boost\archive\binary_oarchive.hpp>
#include <boost\archive\binary_iarchive.hpp>
#include <fstream>
#include <memory>
#include <vector>

class A
{
public:
    A() {}
    A(const A& rhs) = delete;

    int someInt = 0;
    A* parent = nullptr;
    std::vector<A*> children;

    template <class Archive>
    void serialize(Archive& archive, const unsigned int version)
    {
        archive & someInt;
        archive & parent;
        int count = children.size();
        archive & count;
        children.resize(count);
        for (int i = 0; i < count; ++i)
        {
            A* ptr = children[i];
            archive & ptr;
            children[i] = ptr;
        }
    }
};


int main()
{
    A* newA = new A();
    newA->someInt = 0;

    A* newPtr = new A();
    newPtr->someInt = 5;
    newPtr->parent = newA;

    newA->children.push_back(newPtr);

    //  Save.
    std::ofstream outputFile("test", std::fstream::out | std::fstream::binary);
    if (outputFile.is_open())
    {
        boost::archive::binary_oarchive outputArchive(outputFile);

        //  Serialize objects.
        outputArchive << newA;
        outputFile.close();
    }

    delete newA;
    delete newPtr;

    A* loadedPtr = nullptr;

    //  Load.
    std::ifstream inputFile("test", std::fstream::binary | std::fstream::in);
    if (inputFile && inputFile.good() && inputFile.is_open())
    {
        boost::archive::binary_iarchive inputArchive(inputFile);

        //  Load objects.
        inputArchive >> loadedPtr;
        inputFile.close();
    }

    return 0;
}

单步执行代码。孩子的parent 始终为空。

【问题讨论】:

  • 如果有人,任何人可以复制这个问题,请告诉我,因为它让我发疯!
  • FWIW:更新 2 中的程序(加上一些用于验证的断言)works correctly at Coliru 所以问题出在你的最后。
  • 那我到底做错了什么?我已经在两台机器上测试过这个,两个不同的 Boost 版本......也许我构建错了?

标签: c++ serialization boost c++11


【解决方案1】:

好吧,显然我成为了另一个不幸的错误的牺牲品。根据latest Boost release page,Boost 1.55 还没有适用于 VS2013 的序列化库。 谈论浪费的时间......

Visual Studio 2013/Visual C++ 12 的已知错误

Visual Studio 2013 在发布过程中很晚才发布,所以存在几个 尚未解决的问题。其中包括:

由于缺少包含,序列化无法编译。

【讨论】:

  • 我没有看到你从哪里得到这些信息。您能否引用/参考告诉您 VS2013 没有序列化库的特定文档位置? (我可以看到 VC12 还没有列在 (additional) test compilers 下,但那是另一回事)
  • 用我链接的页面顶部的引用更新了我的答案。
【解决方案2】:

我过去也遇到过同样的问题,但我没有找到开箱即用的可靠解决方案。但以下小技巧可以正常工作 - 您可以分别指定序列化和反序列化函数(不使用默认值模板和 & 运算符):

//! Serialization
void A::serialize(xml_oarchive& ar, const unsigned int version)
{
   ar << value;
}
//! De-serialization
void A::serialize(xml_iarchive& ar, const unsigned int version)
{
   ar >> value;
}

然后,您可以在反序列化方法中指定恢复指向父对象的指针,如下所示:

//! Serialization
void A::serialize(xml_oarchive& ar, const unsigned int version)
{
   ar << children;
}
//! De-serialization
void A::serialize(xml_iarchive& ar, const unsigned int version)
{
   ar >> children;
   for(var child: children)
       child->parent = this;
}

【讨论】:

  • 您使用的是哪个版本的 boost?我在使用 x64 的预构建 1.54 时尝试了它,并且正确填充了 parent 指针。顺便说一句,我实际上正在使用您的解决方法,但我也遇到了在指针首先反序列化很好,然后在完全不同的地方反序列化的地方(没有像这样的简单解决方法),第二个最终为 null 或未初始化。
  • 我认为是 boost 1.33,但我不确定。顺便说一句,我已经在 gcc (mingw64) 上使用 1.54 和 1.55 测试了您的代码,它可以正常工作。而且我还检查了 boost 序列化中的默认引用计数,它适用于树结构、双链表和循环列表 - 所有指向父元素的指针都已正确恢复。
  • 是的,问题出在 VC++12。
  • 这种方式不需要拆分序列化,这不是Boost推荐的方式。一种首选方法是在必要时使用if (typename Archive::is_loading())if (typename Archive::is_saving()) 下的某些部分进行统一的函数序列化。这样,“保存”和“加载”之间不一致的可能性很小。或者,您可以使用名称为“save”和“load”的单独函数,然后使用 Boost.Serialize 宏 BOOST_SERIALIZATION_SPLIT_FREE 或 BOOST_SERIALIZATION_SPLIT_MEMBER 之一将它们组合起来。
【解决方案3】:

这已在开发分支上解决。

看。 https://svn.boost.org/trac/boost/ticket/9601

【讨论】:

    【解决方案4】:

    如果您使用 Boost 序列化,一切都应该开箱即用。你的类序列化可能看起来像

    #include <boost/serialization/vector.hpp>
    void serialize (Archive&ar, unsigned int version)
    {
         //declare possible derived types of nodes
         ar & parent;
         ar & children;
    }
    

    存档将散列指针,因此它不会多次创建每个元素。

    一个重要的细节:如果你的节点可以是某种派生类型,你需要教存档在 // 地方期望什么类型, 请参阅 boost 文档中有关通过指向基类型的指针序列化派生类型的详细信息。

    如果您确定您的树结构正确且自包含(根节点为 NULL 作为父节点,所有其他节点都是其各自“父节点”的“子节点”等),那么您可以更有效地组织代码:

    #include <boost/serialization/vector.hpp>
    void serialize (Archive&ar, unsigned int version)
    {
         //declare possible derived types of nodes (either here or at archive creation point)        
         ar & children;
         if (typename Archive::is_loading())
         {
              for (auto child : children)
                 child->parent = this;
         }
    }
    

    (我假设您在构造函数中将“父级”设置为 NULL)。

    我没有使用 VS 2013 测试此代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-21
      • 2011-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-23
      相关资源
      最近更新 更多