Boost::Serialization Help

Hi!

I'm using Boost Serialization (https://www.boost.org/doc/libs/1_71_0/libs/serialization/doc/index.html) and I had a question about serializing classes that contain polymorphic data.

Simply, suppose we had 4 classes:

1
2
3
4
5
Class Base { }
Class OptionOne : public Base{ }
Class OptionTwo : public Base{ }

Class Container { std::unique_ptr<Base> m_ptr; }


Where class `Container` holds a pointer to a base class which could instantiate a derived class `OptionOne` or `OptionTwo`.


What would the correct way be for `Container` to properly serialize `m_ptr` such that when it was deserialized it would be reconstituted properly.

Per Boost's documentation, I found this demo: https://www.boost.org/doc/libs/1_71_0/libs/serialization/example/demo.cpp

Which, if you search "Polymorphic", you'll find their example. Its just not very clear what's happening, so I was hoping to get someone's help and a bit of an explanation as to how it works so that I can scale that example appropriately to my app.

Best,

Aaron


What would the correct way be for `Container` to properly serialize `m_ptr` such that when it was deserialized it would be reconstituted properly.


First of all, serialization of the Base objects would be the responsibility of the Base, OptionOne and Option2 classes, not the Container, per se.

In your serialization code, add some bit of information that distinguishes which derived class is being written. When you deserialize, read that bit of information and construct the proper derived class, and populate it based on the data in the serialized stream.
So, I understand what you're saying in theory, but in practice I'm not really sure how to apply what you've said.

To give you a more concrete example, my code is basically a player that can be any number of races (chosen at run-time).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Player
{
public:
  [...]

private:	
  friend class boost::serialization::access;
  template<typename Archive>
  void serialize(Archive& ar, const unsigned version)
  {
	ar & m_name;
	ar & *m_race; // Seems to serialize fine, but I'm not sure how to deserialize.
  }

  std::string m_name;
  Race* m_race; // Could be anything: new Human(), new Elf(), new Orc()
};


1
2
3
4
5
6
7
8
9
10
11
12
13
class Human : public Race
{
public:
  [...]

private:
  friend class boost::serialization::access;
  template<typename Archive>
  void serialize(Archive& ar, const unsigned version)
  {
	ar & boost::serialization::base_object<Race>(*this);
  }
};


... Many More Race Classes Inherit From Race ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Race
{
public:
  [...]

private:
  friend class boost::serialization::access;
  template<typename Archive>
  void serialize(Archive& ar, const unsigned version)
  {
	ar & m_name;
  }
  std::string m_name;
};


The concept of this code compiles and serializes just fine, but when I try to re-load the same player, I get a seg fault. This is likely because m_race first has to allocate memory for a race using new then fill that object with data from the serialization stream as you've suggested. My problem is that I'm unsure what race to create with new, as well as how to do that using only a single serialization function.
For posterity, It's worth noting that I think I figured it out. Boosts documentation isn't really comprehensive on the subject, but supplemented with Stack overflow, I think its working, though I still have some questions on HOW exactly.

It seems as though that boost's archive system serializes the MOST derived object of a base class if it is polymorphic (https://www.boost.org/doc/libs/1_71_0/libs/serialization/doc/serialization.html#derivedpointers), so I think a lot of this is just handled by the library by default and all you have to do is "register" the derived classes with the archive beforehand.

The one piece I'm confused on is the memory management piece. The first time a player is created, its race will be set with m_race = new Human(); and thus `m_race` will point to some allocated memory on the heap, but when deserializing an object that contains a pointer to a polymorphic type, it looks like the library uses "placement new" to construct the object in memory that (I ASSUME) the library creates for you?? Because it certainly doesn't appear to be happening explicitly in the serialize function itself. (https://www.boost.org/doc/libs/1_71_0/libs/serialization/doc/serialization.html#constructors)

1
2
3
4
5
6
7
8
9
// load data required for construction and invoke constructor in place
template<class Archive, class T>
inline void load_construct_data(
    Archive & ar, T * t, const unsigned int file_version
){
    // default just uses the default constructor to initialize
    // previously allocated memory. 
    ::new(t)T();
}


Because there is no "placement delete" this creates a disparity with how I manage the memory I think...As if I try to delete a pointer that has been allocated with placement new, I *think* I get a segfault, but it's obviously correct to delete a pointer that was created explicitly with new.

I tried something like if(m_race) delete m_race; but that also seemed to toss a segfault. I dunno. I'll do some more work around it and see.

In any case, I'm assuming the

::new(t)T(); portion of the code evaluates to something like ::new(Human)Race(); such that a derived Human() type of base class Race will get constructed.

Sorry for rambling. Just trying to record what I'm in the process of learning so that if someone finds this thread they'll at least have my ramblings.
Topic archived. No new replies allowed.