Boost :: Serialization:如何避免双重保存指针? (并得到一个free.c的错误) [英] Boost::Serialization: How to avoid the double-saving of a pointer? (And getting a free.c error)

查看:157
本文介绍了Boost :: Serialization:如何避免双重保存指针? (并得到一个free.c的错误)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我可以加载和保存这个列表没有任何问题。使用BOOST_CLASS_EXPORT(...)宏,一切正常,直到我添加以下:




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



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



(停止beeing这么抽象:这些是一些游戏对象,他们有一些称为区域的对象引用,类。)

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






现在我将序列化列表,每个对象都被单独序列化。

  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&区; //如果我添加这行,它会崩溃
}

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

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

  ar& area; 





所以Area将被序列化两次,将从列表中保存,然后从对象中保存。



如何避免这种情况?是否可以保存第一次整个对象和第二次只有指针?



或者我应该尝试完全不同的内容(从ID列表中获取指针)

解决方案

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

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

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

〜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)

注意




  • 列表拥有区域所有权。 (因此,make list不可复制;否则复制列表将导致双删除)

  • em>到 List 中的 Area 对象之一。因此不应删除销毁区域(列表拥有它们)



示例程序: / p>

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

std :: unique_ptr< M_Character> obj,往返

//构建原始obj
obj.reset(新GameObject(l.areas [3])); //从列表中共享区域指针

std :: string const serialized = serialize(obj.get());
std :: cout<<序列化 '\\\
';
std :: cout<< ------------------------------------------------- \\\
;

//创建往返
roundtrip.reset(deserialize(serialized));
std :: cout<< EQUAL?< std :: boolalpha< (serialized == serialize(roundtrip.get()))< \\\
;
}

您会注意到这个运行是否正常( 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


但嘿!在序列化指针时,不是Boost序列化执行对象跟踪


是的,它确实。 (这很容易被忽视),它将在同一对象图中的指针 。因此,您可以通过


  1. 使用Gameobject将列表序列化(打破封装,容易出错)来解决问题

  2. 创建一个作为容器的超对象(或技术上:对象图的根)

      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&列表;
    ar&物体;
    }
    };



    现在我们可以序列化/反序列化整个对象图如您所料: 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


p>

完整代码清单



  #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(){} //仅在反序列化中使用
friend boost :: serialization :: access;
template< class Archive>
void serialize(Archive& ar,unsigned){ar& ID; }
};

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

〜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&区域; }
};

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&区;
}
};

BOOST_CLASS_EXPORT_GUID(GameObject,GameObject)
#include< sstream>

struct World:boost :: noncopyable
{
列表列表;
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&列表;
ar&物体;
}
};

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()
{
世界世界;
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]); //从列表中共享区域指针

std :: string const serialized = serialize(world);
std :: cout<<序列化 '\\\
';
std :: cout<< ------------------------------------------------- \\\
;

//创建roundtrip
世界往返;
deserialize(serialized,roundtrip);
std :: cout<< EQUAL?< std :: boolalpha< (serialized == serialize(roundtrip))< \\\
;
}


I currently have a stl:list, which contains some base objects and some derived classes as well.

I can load and save this list without any problems. With the BOOST_CLASS_EXPORT(...) macro, everything is working properly, until i add the following:


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

I need some objects which contain pointers of other objects in the list.

(To stop beeing so abstract: These are some "Game-Objects" and they have some References to Objects called "Areas", both are derived from the elementary Game-Class.)
--------------------------------------------------------------------------------------


So now I am serializing the list, and every object gets serialized individually.

ar  & characterList;

The Game-Object contains the following code:

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
}

So I am trying to save to pointer to the Area, but after I add these lines, the program will crash and I will get to free.c, which tells me that I freed the same pointer twice. Boost will also give me an unregistered class error. (Altough I exported the Area Class, and it also worked without

ar & area;

)

So the Area is going to get serialized twice because first the Area will get saved from the list and then from the Object.

How can I avoid this situation? Is it possible to save the first time the whole object and the second time only the pointer?

Or should I try something complete different (Get the pointer from the list with IDs)

解决方案

Okay, I've created a demo for you:

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")

Note

  • List has ownership of the areas. (Therefore, make List non-copyable; otherwise copying List would lead to double-deletes)
  • GameObject refers to one of the Area objects in a List. Therefore it should not delete the area on destruction (the list owns them!)

Sample program:

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";
}

You will note how this runs fine (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

What you won't see on Coliru, though, is that this leaks memory. Valgrind tells you that 4 bytes are lost that were allocated during deserialization - obviously this is the Area from withing the GameObject.

But hey! Isn't Boost Serialization to do object tracking when serializing pointers?

Good question. Yes it does. But (and this is easy to overlook), it will only do so for pointers within the same object graph. Therefore, you can solve the problem by

  1. serializing the List with the Gameobject (breaks encapsulation, error prone)
  2. make a super-object that serves as the "container" (or technically: the root of the object graph)

    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;
            }
    };
    

    Now we can serialize/deserialize the whole object graph ("World") and the object tracking will work as you expected: 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
    

And no more memory leaks!

Full Code Listing

#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";
}

这篇关于Boost :: Serialization:如何避免双重保存指针? (并得到一个free.c的错误)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆