Boost 序列化在循环恢复时失败

问题描述 投票:0回答:1

我尝试为两个类

Geometry
Dimension
实现一个序列化器,它们之间具有循环依赖关系。这意味着
Geometry
可以有一个
Dimension
并且
Dimension
知道它的
Geometry
。另外,我有
DataModel
,其中包含几何形状和尺寸的向量。我的课程是这样的:

class IGeometry
{
public:
    virtual ~IGeometry() = default;
};

class IDimension
{
public:
    virtual ~IDimension() = default;
    virtual const std::vector<IGeometry*>& GetGeometries() const = 0;
};

class Dimension : public virtual IDimension
{
public:
    Dimension(std::vector<IGeometry*> f) : geometries{ std::move(f) } {}
    ~Dimension() override = default;

    const std::vector<IGeometry*>& GetGeometries() const override 
    {
        return geometries;
    }

private:
    std::vector<IGeometry*> geometries;
};

class Geometry : public virtual IGeometry
{
public:
    ~Geometry() override {}

    void AddDimension(IDimension* dimension)
    {
        dimensions.emplace_back(dimension);
    }
    const std::vector<IDimension*>& GetDimensions() const { return dimensions; }
private:
    std::vector<IDimension*> dimensions{};
};

struct DataModel
{
    std::vector<IGeometry*> geometries;
    std::vector<IDimension*> dimensions;
};

我正在使用来自 boost 的非侵入式序列化,如下所示:

BOOST_SERIALIZATION_SPLIT_FREE(Geometry)

BOOST_CLASS_EXPORT(Dimension)
BOOST_CLASS_EXPORT(Geometry)

namespace boost
{
namespace serialization
{

template<class Archive>
void serialize(Archive& ar, IGeometry& g, const unsigned int version){ }

template<class Archive>
void serialize(Archive& ar, IDimension& d, const unsigned int version){ }

template<class Archive>
void save(Archive& ar, const Geometry& g, const unsigned int version)
{
    ar& boost::serialization::base_object<IGeometry>(g);
    ar& g.GetDimensions();
}
template<class Archive>
void load(Archive& ar, Geometry& g, const unsigned int version)
{
    ar& boost::serialization::base_object<IGeometry>(g);

    std::vector<IDimension*> dimensions;
    ar& dimensions;
    for(auto* dimension : dimensions)
    {
        g.AddDimension(dimension);
    }
}

template<class Archive>
void serialize(Archive& ar, Dimension& d, unsigned int version)
{
    ar& boost::serialization::base_object<IDimension>(d);
}

template<class Archive>
void save_construct_data(Archive& ar, const Dimension* t, const unsigned int)
{
    ar& t->GetGeometries();
}

template<class Archive>
void load_construct_data(Archive& ar, Dimension* t, const unsigned int file_version)
{
    std::vector<IGeometry*> foos;
    ar& foos;
    ::new(t)Dimension(foos);
}

template<class Archive>
void serialize(Archive& ar, DataModel& model, const unsigned int version)
{
    ar& model.dimensions & model.geometries ;
}

}
}

void SaveModel(const DataModel& model)
{
    std::ofstream ofs("filename");
    boost::archive::text_oarchive oa(ofs);
    oa << model;
}

void RestoreModel(DataModel& model)
{
    std::ifstream ofs("filename");
    boost::archive::text_iarchive ia(ofs);
    ia >> model;
}

int main()
{
    {
        auto* p0 = new Geometry();
        auto* p1 = new Geometry();

        auto* d2 = new Dimension({ p0, p1 });

        p0->AddDimension(d2);
        p1->AddDimension(d2);

        DataModel model;

        model.geometries.emplace_back(p0);
        model.geometries.emplace_back(p1);
        model.dimensions.emplace_back(d2);

        SaveModel(model);
    }

    DataModel model2;
    RestoreModel(model2);

    return 0;
}

main() 中的示例在尝试恢复模型时失败,出现以下异常:

Exception thrown at 0x00007FF7F62BA385 in boost.exe: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF.

我的猜测,在恢复时,Boost 会尝试用

Dimension
中的 2 个几何图形对
load_construct_data
进行反序列化。然后它尝试反序列化其中一个几何图形,该几何图形再次以
Dimension
作为参考,当然当前尚未构建。

我是新来的

boost::serialization

我的问题是:

  • 我的数据模型的序列化是否可行?
  • 通用序列化代码是否“正确”(关于接口等)
  • 我在反序列化循环依赖时做错了什么?

没有抽象类它工作得很好!

c++ boost boost-serialization
1个回答
0
投票

循环引用很好。您的问题似乎来自于虚拟基类。

特别是在这里评论

virtual
关键字:

class Dimension : public /*virtual*/ IDimension {

让问题消失。我唯一的预感是,在加载构造数据发生之前,需要以某种方式构造基础对象。

事实上,即使使用虚拟基础,消除加载/保存构造数据的需要也确实有效:

住在Coliru

仔细想想,也有道理:

  • 序列化 Dimension 序列化相关的几何图形
  • 几何图形在加载构造数据中读回。因为当时对象还没有被构造出来,所以对象跟踪还不可能发生
  • 但是,反序列化几何图形意味着间接反序列化其相关维度,这需要正确的对象跟踪已完成维度。由于情况并非如此,因此将采用错误的分支,并且“有效损坏”流会导致代码调用未定义的行为

总结

两种解决方案:

  • 删除(不必要的?)
    virtual
    基类修饰符
  • 使用友元访问或侵入式序列化来序列化相关实体
© www.soinside.com 2019 - 2024. All rights reserved.