Boost::Serialization: How to avoid the double-saving of a pointer? (And getting a free.c error)

萝らか妹 提交于 2019-12-11 05:07:25

问题


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)


回答1:


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


来源:https://stackoverflow.com/questions/23456360/boostserialization-how-to-avoid-the-double-saving-of-a-pointer-and-getting

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!