Boost :: Serialization:如何避免指针的双重保存? (并获得free.c错误)

时间:2014-05-04 12:44:04

标签: c++ boost-serialization

我目前有一个stl:list,它包含一些基础对象和一些派生类。

我可以毫无问题地加载和保存此列表。使用BOOST_CLASS_EXPORT(...)宏,一切正常,直到我添加以下内容:


------------------------------------------- -------------------------------------------

我需要一些包含列表中其他对象指针的对象。

(为了停止如此抽象:这些是#34;游戏对象"他们有一些对象被称为"区域",两者都来自基本的游戏类。 )
---------------------------------------------- ----------------------------------------


所以现在我正在序列化列表,并且每个对象都被单独序列化。

ar  & characterList;

Game-Object包含以下代码:

template<class Archive>
void save(Archive & ar, const unsigned int version) const {
    ar & boost::serialization::base_object<M_Character>(*this);
    ar & area; // If I add this line, it will crash
}

template<class Archive>
void load(Archive & ar, const unsigned int version) const {
    ar & boost::serialization::base_object<M_Character>(*this);
    ar & area; // This one as well
}

所以我试图保存到指向区域的指针,但是在我添加这些行之后,程序将崩溃,我将转到free.c,它告诉我,我释放了两次相同的指针。 Boost还会给我一个未注册的类错误。 (Altough我导出了Area Class,它也没有

ar & area;

因此,区域将被序列化两次,因为首先区域将从列表中保存,然后从对象中保存。

我该如何避免这种情况?有可能第一次保存整个对象而第二次只保存指针吗?

或者我应该尝试完全不同的东西(从带有ID的列表中获取指针)

1 个答案:

答案 0 :(得分:1)

好的,我已经为您创建了一个演示:

struct Area
{
    Area(int i):id(i) {}
    int id;
};

struct List : boost::noncopyable
{
    std::vector<Area*> areas;

    ~List() {
        std::for_each(areas.begin(), areas.end(), std::default_delete<Area>());
    }
};

struct M_Character {
    virtual ~M_Character() {} 
};

struct GameObject : M_Character, boost::noncopyable
{
    Area* area;

    GameObject(Area* area = nullptr) : area(area) {}
};

BOOST_CLASS_EXPORT_GUID(GameObject, "GameObject")

请注意

  • 列表包含areas所有权。 (因此,使列表不可复制;否则复制List将导致双删除)
  • GameObject 引用到Area中的List个对象之一。因此不应该删除销毁区域(列表拥有它们!)

示例程序:

int main()
{
    List l;
    for (int i = 0; i < 10; ++i)
        l.areas.push_back(new Area(i));

    std::unique_ptr<M_Character> obj, roundtrip;

    // build original obj
    obj.reset(new GameObject(l.areas[3])); // sharing the area pointer from the list

    std::string const serialized = serialize(obj.get());
    std::cout << serialized << '\n';
    std::cout << "-------------------------------------------------\n";

    // create roundtrip
    roundtrip.reset(deserialize(serialized));
    std::cout << "EQUAL? " << std::boolalpha << (serialized == serialize(roundtrip.get())) << "\n";
}

你会注意到它如何正常运行( Live On Coliru ):

clang++ -std=c++11 -Os -Wall -pedantic main.cpp -lboost_system -lboost_serialization && ./a.out
22 serialization::archive 10 1 10 GameObject 1 0
0 1 0
1 2 1 0
2 3
-------------------------------------------------
EQUAL? true

赢了但在Coliru上看到的是,这会泄漏内存。 Valgrind告诉你,在反序列化过程中丢失了4个字节 - 显然这是Area来自GameObject

  

但是嘿!在序列化指针时,是否提升序列化以进行对象跟踪

好问题。是的,它确实。 (这很容易被忽略),对同一对象图中的指针执行此操作。因此,您可以通过

解决问题
  1. 使用Gameobject序列化List(打破封装,容易出错)
  2. 制作一个充当&#34;容器的超级对象&#34; (或技术上:对象图的根)

    struct World : boost::noncopyable
    {
        List list;
        GameObject* the_object;
    
        World() : the_object(nullptr) {}
        ~World() { delete the_object; }
    
    private:
        friend boost::serialization::access;
        template<class Archive>
            void serialize(Archive & ar, unsigned) {
                ar & list;
                ar & the_object;
            }
    };
    

    现在我们可以序列化/反序列化整个对象图(&#34; World&#34;),对象跟踪将按预期工作: Live On Coliru

    clang++ -std=c++11 -Os -Wall -pedantic main.cpp -lboost_system -lboost_serialization   && ./a.out
    22 serialization::archive 10 0 0 0 0 0 0 10 0 3 1 0
    0 0 3
    1 1 3
    2 2 3
    3 3 3
    4 4 3
    5 5 3
    6 6 3
    7 7 3
    8 8 3
    9 9 4 1 0
    10 0 0 3 3
    -------------------------------------------------
    EQUAL? true
    
  3. 没有更多的内存泄漏!

    完整代码清单

    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/serialization/serialization.hpp>
    #include <boost/serialization/export.hpp>
    #include <boost/serialization/vector.hpp>
    
    struct Area
    {
        Area(int i):id(i) {}
        int id;
      private:
        Area() { } // used only in deserialization
        friend boost::serialization::access;
        template<class Archive>
            void serialize(Archive & ar, unsigned) { ar & id; }
    };
    
    struct List : boost::noncopyable
    {
        std::vector<Area*> areas;
    
        ~List() {
            std::for_each(areas.begin(), areas.end(), std::default_delete<Area>());
        }
    
      private:
        friend boost::serialization::access;
        template<class Archive>
            void serialize(Archive & ar, unsigned) { ar & areas; }
    };
    
    struct M_Character {
        virtual ~M_Character() {} 
      private:
        friend boost::serialization::access;
        template<class Archive>
            void serialize(Archive & /*ar*/, unsigned) { }
    };
    
    struct GameObject : M_Character, boost::noncopyable
    {
        Area* area;
    
        GameObject(Area* area = nullptr) : area(area) {}
    
      private:
        friend boost::serialization::access;
        template<class Archive>
            void serialize(Archive & ar, unsigned) {
                ar & boost::serialization::base_object<M_Character>(*this);
                ar & area;
            }
    };
    
    BOOST_CLASS_EXPORT_GUID(GameObject, "GameObject")
    #include <sstream>
    
            struct World : boost::noncopyable
            {
                List list;
                GameObject* the_object;
    
                World() : the_object(nullptr) {}
                ~World() { delete the_object; }
    
            private:
                friend boost::serialization::access;
                template<class Archive>
                    void serialize(Archive & ar, unsigned) {
                        ar & list;
                        ar & the_object;
                    }
            };
    
    std::string serialize(World const& w)
    {
        std::stringstream ss;
        boost::archive::text_oarchive oa(ss);
    
        oa << w;
    
        return ss.str();
    }
    
    void deserialize(std::string const& input, World& w)
    {
        std::stringstream ss(input);
        boost::archive::text_iarchive ia(ss);
    
        ia >> w;
    }
    
    int main()
    {
        World world;
        for (int i = 0; i < 10; ++i)
            world.list.areas.push_back(new Area(i));
    
        // build original obj
        world.the_object = new GameObject(world.list.areas[3]); // sharing the area pointer from the list
    
        std::string const serialized = serialize(world);
        std::cout << serialized << '\n';
        std::cout << "-------------------------------------------------\n";
    
        // create roundtrip
        World roundtrip;
        deserialize(serialized, roundtrip);
        std::cout << "EQUAL? " << std::boolalpha << (serialized == serialize(roundtrip)) << "\n";
    }