2

I am wondering about this. I have a C++ program with a number of data structs which derive from a common root and I need to serialize them using Boost. Each one has an inline member function to accept a visitor (so I can visit the structure without a "switch" statement).

The objects look like this:

In the .h file:

// Graphic component.
struct GraphicComponent : public Component {
  ... data members ...
  void accept(ComponentVisitor &vis) { vis.visitGraphicComponent(*this); }

 private:
  // Serialization routine.
  friend class boost::serialization::access;

template<class Archive>
  void serialize(Archive &a, const unsigned int v);
};
BOOST_CLASS_EXPORT_KEY(GraphicComponent)

// Position component.
struct PositionComponent : public Component {
  ... data members ...
  void accept(ComponentVisitor &vis) { vis.visitPositionComponent(*this); }

 private:
  // Serialization routine.
  friend class boost::serialization::access;

template<class Archive>
  void serialize(Archive &a, const unsigned int v);
};
BOOST_CLASS_EXPORT_KEY(PositionComponent)

...

In the .cpp file, I declare the "serialize" routines:

BOOST_CLASS_EXPORT_IMPLEMENT(GraphicComponent)
BOOST_CLASS_EXPORT_IMPLEMENT(PositionComponent)
...

template<class Archive>
  void GraphicComponent::serialize(Archive &a, const unsigned int v)
  {
    a & boost::serialization::base_object<Component>(*this);
    ... serialize data members ...
  }

template<class Archive>
  void PositionComponent::serialize(Archive &a, const unsigned int v)
  {
    a & boost::serialization::base_object<Component>(*this);
    ... serialize data members ...
  }

...

I also include the Boost archive through a common header. As far as I can tell, everything looks right. There's also a "BOOST_SERIALIZATION_ASSUME_ABSTRACT" on the base Component, as "accept" is pure virtual.

When I run the program and get to the point where it serializes this stuff, I get

 what():  unregistered class - derived class not registered or exported

Serialization occurs through a pointer to the base Component.

I've heard troubles involving Boost serialization and "libraries". The build system I was using, CMake, is set up to compile the program by assembling its subcomponents into libraries and then putting those together into a single executable to make the final program. Could that be the problem?

Also, Component derives from std::enable_shared_from_this (that's C++11 STL, not Boost) -- could this be the problem? If so, what can be done about it?

m.s.
  • 16,063
  • 7
  • 53
  • 88
The_Sympathizer
  • 1,191
  • 9
  • 17
  • 1
    post a [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve) – m.s. Jun 22 '15 at 06:43
  • I had a similar problem in my multi-library plus Boost serialization code. I don't know what is the problem cause, but my solution is to create a dummy function, which does nothing, in a source/cpp files where the serialization is implemented and call this function in the `main` function. The dummy function code is `static volatile int g_iDummyInitialization(0); void InitModule00Serialzation(void) { g_iDummyInitialization = 1; }`. In my case it helps. – megabyte1024 Jun 23 '15 at 11:44

3 Answers3

1

In case it helps, here's a working SSCCE (or MCVE as the commenter said):

Live On Coliru

#include <boost/serialization/access.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

struct ComponentVisitor;

struct Component {
    virtual ~Component() = default;
    virtual void accept(ComponentVisitor &v) = 0;
  private:
    // Serialization routine.
    friend class boost::serialization::access;

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

BOOST_SERIALIZATION_ASSUME_ABSTRACT(Component)

struct GraphicComponent;
struct PositionComponent;

struct ComponentVisitor {
    virtual void visitGraphicComponent(GraphicComponent   const &){};
    virtual void visitPositionComponent(PositionComponent const &){};
};

// Graphic component.
struct GraphicComponent : public Component {
    void accept(ComponentVisitor &vis) { vis.visitGraphicComponent(*this); }

  private:
    // Serialization routine.
    friend class boost::serialization::access;

    template <class Archive>
    void serialize(Archive &a, const unsigned int v);
};
BOOST_CLASS_EXPORT_KEY(GraphicComponent)

// Position component.
struct PositionComponent : public Component {
    void accept(ComponentVisitor &vis) { vis.visitPositionComponent(*this); }

  private:
    // Serialization routine.
    friend class boost::serialization::access;

    template <class Archive>
    void serialize(Archive &a, const unsigned int v);
};

BOOST_CLASS_EXPORT_KEY(PositionComponent)

/////////////////////////////////////////////////////

BOOST_CLASS_EXPORT_IMPLEMENT(GraphicComponent)
BOOST_CLASS_EXPORT_IMPLEMENT(PositionComponent)
//...

template <class Archive>
void GraphicComponent::serialize(Archive &a, const unsigned int)
{
    a &boost::serialization::base_object<Component>(*this);
    //... serialize data members ...
}

template <class Archive>
void PositionComponent::serialize(Archive &a, const unsigned int)
{
    a &boost::serialization::base_object<Component>(*this);
    //... serialize data members ...
}

#include <boost/make_shared.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include <sstream>

int main() {
    std::stringstream ss;

    {
        std::vector<boost::shared_ptr<Component> > components {
            boost::make_shared<GraphicComponent>(),
                boost::make_shared<PositionComponent>(),
                boost::make_shared<PositionComponent>(),
                boost::make_shared<GraphicComponent>(),
        };

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

    {
        std::vector<boost::shared_ptr<Component> > deserialized;

        boost::archive::text_iarchive ia(ss);
        ia >> deserialized;

        struct printer : ComponentVisitor {
            void visitPositionComponent(PositionComponent const & /*pc*/){ std::cout << __PRETTY_FUNCTION__ << "\n"; }
            void visitGraphicComponent(GraphicComponent   const & /*gc*/){ std::cout << __PRETTY_FUNCTION__ << "\n"; }
        } print;

        for (auto c : deserialized)
            c->accept(print);
    }
}

Prints

virtual void main()::printer::visitGraphicComponent(const GraphicComponent&)
virtual void main()::printer::visitPositionComponent(const PositionComponent&)
virtual void main()::printer::visitPositionComponent(const PositionComponent&)
virtual void main()::printer::visitGraphicComponent(const GraphicComponent&)

as expected

Notes

  1. Actually, since you only use the serialization in specific TUs, you could consider using non-intrusive serialization:

    Live On Coliru

    struct ComponentVisitor;
    
    struct Component {
        virtual ~Component() = default;
        virtual void accept(ComponentVisitor &v) = 0;
    };
    
    struct GraphicComponent;
    struct PositionComponent;
    
    struct ComponentVisitor {
        virtual void visitGraphicComponent(GraphicComponent   const &){};
        virtual void visitPositionComponent(PositionComponent const &){};
    };
    
    struct GraphicComponent : public Component {
        void accept(ComponentVisitor &vis) { vis.visitGraphicComponent(*this); }
    };
    
    struct PositionComponent : public Component {
        void accept(ComponentVisitor &vis) { vis.visitPositionComponent(*this); }
    };
    
    /////////////////////////////////////////////////////
    // in the CPP
    #include <boost/serialization/access.hpp>
    #include <boost/serialization/export.hpp>
    #include <boost/serialization/base_object.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/archive/text_oarchive.hpp>
    
    BOOST_SERIALIZATION_ASSUME_ABSTRACT(Component)
    BOOST_CLASS_EXPORT(GraphicComponent)
    BOOST_CLASS_EXPORT(PositionComponent)
    
    namespace boost { namespace serialization {
        template <class Archive> void serialize(Archive &, Component&, const unsigned int) {}
        template <class Archive> void serialize(Archive &a, GraphicComponent& obj, const unsigned int) {
            a &boost::serialization::base_object<Component>(obj);
        }
        template <class Archive> void serialize(Archive &a, PositionComponent& obj, const unsigned int) {
            a &boost::serialization::base_object<Component>(obj);
        }
    } }
    

    Which is considerably cleaner

  2. If you still want to access private members from inside serialize cf. e.g.

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added two notes about code organization. I did this because it looks like you're trying to "hide" boost serialization things from the header. You can achieve this much clearer :) – sehe Jun 22 '15 at 08:27
0

This is a partial answer as it doesn't explain exactly why it failed. I have managed to solve the problem by compiling the program as a single program instead of a bunch of libraries that are then statically linked together, which is how I thought I had to do it with the build system I was using since the documentation that was available online for the system was terse and when I put together the makefiles, I wasn't sure exactly how to do it. I suspect it has something to do with Boost's trouble dealing with this kind of code in libraries.

The_Sympathizer
  • 1,191
  • 9
  • 17
  • this explains it. Since your program doesn't refer explicitly to the derived class, the code for the derived class isn't brought in by the linker. The export macros take care of this for DLLS and should do the same for static linking. But without knowing which linker, compiler, compiler switches related to symbol visibility etc it would be hard to give more information. – Robert Ramey Nov 24 '15 at 22:56
0

You need to include archive class headers in your library codes. https://www.boost.org/doc/libs/1_75_0/libs/serialization/doc/special.html#export

Placing BOOST_CLASS_EXPORT in library code will have no effect unless archive class headers are also included. So when building a library, one should include all headers for all the archive classes which he anticipates using.

Skywave
  • 193
  • 1
  • 7