问题
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 copyingList
would lead to double-deletes) - GameObject refers to one of the
Area
objects in aList
. 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
- serializing the List with the Gameobject (breaks encapsulation, error prone)
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