Boost serialize child class
Asked Answered
V

1

1

I have base class User which is serializable :

class User
{
public:
    User();
    std::string GetLogin() const;
    void SetLogin(std::string login);

protected:
    std::string mLogin;
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & mLogin;

    }
};

This class can be inherited by other class like this :

class UserA : public User
{
    UserA();
private:
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & boost::serialization::base_object<User>(*this);
        ar & mIsSomething;
    }
    bool mIsSomething = true;
}

To handle those user i have a "manager" class which contain a User vector :

class Manager
{
public:

    bool Add(User user);
    bool Remove(unsigned int index);

private:
    std::vector<User> mUsers;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        ar & mUsers;
    }
};

So my manager can be filled with UserA , or UserB (never both at the same time). When i retrieve an element from Manager i simply cast it back to the correct child class. This part is working fine.

But when i want to serialize the Manager class obviously Boost don't know which kind of User i'm trying to serialize and the extra fields from the child class are not serialized.

What are my solution here ?
Does my design is completly wrong ?
Should i specialize my manager class to something like this ?

class Manager
    {
        bool Add(UserA user);
        bool Add(UserB user);
    private:
        std::vector<UserA> mUsersA;
        std::vector<UserB> mUsersB;
}
Vault answered 27/10, 2015 at 10:12 Comment(2)
livecoding.tv/seheSeringapatam
I'd suggest making class Manager a template <typename UserType> class ManagerBrooking
S
1

So my manager can be filled with UserA , or UserB (never both at the same time)

No it can't:

std::vector<User> mUsers;

stores User objects by value. See What is object slicing?.

Thoughts

I'd also suggest templating the Manager on the concrete user type, but seeing how you use an actual type hierarchy, it seems you might be looking to actually use the runtime polymorphism.

Since serializing polymorphic types is somewhat more involved, let me show you a sample.

It also shows how to use e.g. boost::ptr_vector<> to manage the objects while storing them dynamically.

Live1 on Coliru

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/ptr_container/serialize_ptr_vector.hpp>

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/access.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>

class User
{
public:
    User() {};
    virtual ~User() {}
    std::string GetLogin() const;
    void SetLogin(std::string login);

protected:
    std::string mLogin;
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int /*version*/)
    {
        ar & mLogin;
    }
};

class UserA : public User
{
  public:
    UserA() {};
  private:
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int /*version*/)
    {
        ar & boost::serialization::base_object<User>(*this);
        ar & mIsSomething;
    }
    bool mIsSomething = true;
};

class UserB : public User
{
  public:
    UserB() {};
  private:
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int /*version*/)
    {
        ar & boost::serialization::base_object<User>(*this);
        ar & mIsSomethingElse;
    }
    bool mIsSomethingElse = true;
};

template <typename Tag>
class UserGen : public User
{
  public:
    UserGen() {};
  private:
    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int /*version*/)
    {
        ar & boost::serialization::base_object<User>(*this);
        ar & mIsGen;
    }
    bool mIsGen = true;
};

struct GenA;
struct GenB;
struct GenC;

BOOST_CLASS_EXPORT(User)
BOOST_CLASS_EXPORT(UserA)
BOOST_CLASS_EXPORT(UserB)
BOOST_CLASS_EXPORT(UserGen<GenA>)
BOOST_CLASS_EXPORT(UserGen<GenB>)
BOOST_CLASS_EXPORT(UserGen<GenC>)

#include <boost/type_index.hpp>

class Manager
{
public:

    template <typename User>
    bool Add(User const& user) {
        mUsers.push_back(new User(user));
        return true; // FIXME?
    }
    bool Remove(unsigned int index) {
        if (mUsers.size() > index) {
            mUsers.erase(mUsers.begin()+index);
            return true;
        }
        return false;
    }

    void dump() const {
        for (auto& u : mUsers) {
            std::cout << "user of type " << boost::typeindex::type_id_runtime(u) << "\n";
        }
    }

private:
    boost::ptr_vector<User> mUsers;

    friend class boost::serialization::access;

    template<class Archive>
    void serialize(Archive & ar, const unsigned int /*version*/)
    {
        ar & mUsers;
    }
};

#include <sstream>
#include <iostream>

int main() {
    std::stringstream ss;

    {
        Manager man;
        man.Add(UserA{});
        man.Add(UserB{});
        man.Add(UserGen<GenA>{});
        man.Add(UserGen<GenB>{});
        man.Add(UserGen<GenC>{});

        boost::archive::text_oarchive oa(ss);
        oa << man;
    }

    {
        boost::archive::text_iarchive ia(ss);
        Manager man;
        ia >> man;

        man.dump();
    }
}

Prints

user of type UserA
user of type UserB
user of type UserGen<GenA>
user of type UserGen<GenB>
user of type UserGen<GenC>

1 linking boost 1.59 is somehow failing there :( Thanks @m.s. for figuring out 1.58 still works

Seringapatam answered 27/10, 2015 at 10:19 Comment(5)
Watch what I did in the recorded live session (lots of time wasted trying to het the sample to link on Coliru...) (experiment)Seringapatam
Thanks for your answer. Does the use of ptr_vector is mandatory here ? I'd rather avoid pointer if i can (personal preference).I guess i should also add a delete somewhere on all the pointers right ? Sorry if the questions look dumb , i'm mostly using java.Vault
You should avoid pointers, which is why you should use ptr_vector over e.g. std::vector<User*> :) Look at the documentation for ptr_vector<> boost.org/doc/libs/1_59_0/libs/ptr_container/doc/…Seringapatam
I read the doc of ptr_vector ;) But i was using an std::vector<User> and not a std::vector<User*>. Does it change anything ?. I can't see your recorded session , i got a error 400 from their player :(Vault
YES. Did you read my answer at all? You can use a vector, but then there's really nothing that make it "a child class" (ie. derived class) in any way for all intents and purposes.Seringapatam

© 2022 - 2024 — McMap. All rights reserved.